History

Move all server and controller packages into top-level

Harshavardhana committed 11 years ago

Commit d54488f1 parent d808c3680 files · +20431 −258Browse files

Unified · Split

80 files
  1. Makefile +3 −1
  2. buildscripts/checkdeps.sh +6 −6
  3. controller-main.go +2 −5
  4. controller-router.go +5 −6
  5. controller-rpc-auth.go +1 −1
  6. controller-rpc-donut.go +1 −1
  7. controller-rpc-server.go +1 −1
  8. controller-rpc-version.go +2 −3
  9. controller.go +3 −3
  10. controller_rpc_test.go +59 −64
  11. logger_test.go +5 −1
  12. minio_test.go +2 −11
  13. rpc-request.go +11 −11
  14. server-api-acl.go +1 −1
  15. server-api-bucket-handlers.go +14 −14
  16. server-api-contenttype.go +1 −1
  17. server-api-definitions.go +3 −3
  18. server-api-errors.go +10 −10
  19. server-api-generic-handlers.go +1 −5
  20. server-api-headers.go +3 −4
  21. server-api-httprange.go +1 −1
  22. server-api-logging-handlers.go +1 −1
  23. server-api-object-handlers.go +19 −19
  24. server-api-resources.go +1 −1
  25. server-api-response.go +1 −1
  26. server-api-signature.go +3 −1
  27. server-api-typed-errors.go +1 −1
  28. server-api-utils.go +1 −1
  29. server-api.go +10 −10
  30. server-main.go +4 −8
  31. server-router.go +11 −12
  32. server-signature-v4_test.go +3 −15
  33. server.go +5 −6
  34. server_donut_cache_test.go +9 −10
  35. server_donut_test.go +8 −9
  36. server_signature_v4_test.go +8 −9
  37. vendor.json +28 −0
  38. vendor/gopkg.in/mgo.v2/LICENSE +25 −0
  39. vendor/gopkg.in/mgo.v2/Makefile +5 −0
  40. vendor/gopkg.in/mgo.v2/README.md +4 −0
  41. vendor/gopkg.in/mgo.v2/auth.go +467 −0
  42. vendor/gopkg.in/mgo.v2/auth_test.go +1180 −0
  43. vendor/gopkg.in/mgo.v2/bson/LICENSE +25 −0
  44. vendor/gopkg.in/mgo.v2/bson/bson.go +705 −0
  45. vendor/gopkg.in/mgo.v2/bson/bson_test.go +1605 −0
  46. vendor/gopkg.in/mgo.v2/bson/decode.go +825 −0
  47. vendor/gopkg.in/mgo.v2/bson/encode.go +503 −0
  48. vendor/gopkg.in/mgo.v2/bulk.go +71 −0
  49. vendor/gopkg.in/mgo.v2/bulk_test.go +131 −0
  50. vendor/gopkg.in/mgo.v2/cluster.go +632 −0
  51. vendor/gopkg.in/mgo.v2/cluster_test.go +1657 −0
  52. vendor/gopkg.in/mgo.v2/doc.go +31 −0
  53. vendor/gopkg.in/mgo.v2/export_test.go +33 −0
  54. vendor/gopkg.in/mgo.v2/gridfs.go +755 −0
  55. vendor/gopkg.in/mgo.v2/gridfs_test.go +708 −0
  56. vendor/gopkg.in/mgo.v2/internal/sasl/sasl.c +77 −0
  57. vendor/gopkg.in/mgo.v2/internal/sasl/sasl.go +138 −0
  58. vendor/gopkg.in/mgo.v2/internal/sasl/sasl_windows.c +118 −0
  59. vendor/gopkg.in/mgo.v2/internal/sasl/sasl_windows.go +140 −0
  60. vendor/gopkg.in/mgo.v2/internal/sasl/sasl_windows.h +7 −0
  61. vendor/gopkg.in/mgo.v2/internal/sasl/sspi_windows.c +96 −0
  62. vendor/gopkg.in/mgo.v2/internal/sasl/sspi_windows.h +70 −0
  63. vendor/gopkg.in/mgo.v2/internal/scram/scram.go +266 −0
  64. vendor/gopkg.in/mgo.v2/internal/scram/scram_test.go +67 −0
  65. vendor/gopkg.in/mgo.v2/log.go +133 −0
  66. vendor/gopkg.in/mgo.v2/queue.go +91 −0
  67. vendor/gopkg.in/mgo.v2/queue_test.go +101 −0
  68. vendor/gopkg.in/mgo.v2/raceoff.go +5 −0
  69. vendor/gopkg.in/mgo.v2/raceon.go +5 −0
  70. vendor/gopkg.in/mgo.v2/saslimpl.go +11 −0
  71. vendor/gopkg.in/mgo.v2/saslstub.go +11 −0
  72. vendor/gopkg.in/mgo.v2/server.go +448 −0
  73. vendor/gopkg.in/mgo.v2/session.go +4224 −0
  74. vendor/gopkg.in/mgo.v2/session_test.go +3704 −0
  75. vendor/gopkg.in/mgo.v2/socket.go +677 −0
  76. vendor/gopkg.in/mgo.v2/stats.go +147 −0
  77. vendor/gopkg.in/mgo.v2/suite_test.go +254 −0
  78. vendor/gopkg.in/mgo.v2/syscall_test.go +15 −0
  79. vendor/gopkg.in/mgo.v2/syscall_windows_test.go +11 −0
  80. version_test.go +5 −1
Makefile+3−1
@@ -34,7 +34,8 @@ lint: · 33 unmodified lines
34 34
35cyclo:35cyclo:
36 @echo "Running $@:"36 @echo "Running $@:"
37 @GO15VENDOREXPERIMENT=1 gocyclo -over 25 .37 @GO15VENDOREXPERIMENT=1 gocyclo -over 25 *.go
38 @GO15VENDOREXPERIMENT=1 gocyclo -over 25 pkg
38 39
39build: getdeps verifiers40build: getdeps verifiers
40 @echo "Installing minio:"41 @echo "Installing minio:"
@@ -42,6 +43,7 @@ build: getdeps verifiers · 1 unmodified line
42 43
43test: build44test: build
44 @echo "Running all testing:"45 @echo "Running all testing:"
46 @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) .
45 @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg...47 @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg...
46 48
47gomake-all: build49gomake-all: build
buildscripts/checkdeps.sh+6−6
@@ -21,7 +21,7 @@ _init() { · 20 unmodified lines
21 21
22 ## Minimum required versions for build dependencies22 ## Minimum required versions for build dependencies
23 GCC_VERSION="4.0"23 GCC_VERSION="4.0"
24 CLANG_VERSION="3.5"24 LLVM_VERSION="7.0.0"
25 YASM_VERSION="1.2.0"25 YASM_VERSION="1.2.0"
26 GIT_VERSION="1.0"26 GIT_VERSION="1.0"
27 GO_VERSION="1.5.1"27 GO_VERSION="1.5.1"
@@ -173,7 +173,7 @@ is_supported_arch() { · 145 unmodified lines
173check_deps() {173check_deps() {
174 check_version "$(env go version 2>/dev/null | sed 's/^.* go\([0-9.]*\).*$/\1/')" "${GO_VERSION}"174 check_version "$(env go version 2>/dev/null | sed 's/^.* go\([0-9.]*\).*$/\1/')" "${GO_VERSION}"
175 if [ $? -ge 2 ]; then175 if [ $? -ge 2 ]; then
176 MISSING="${MISSING} golang(1.5)"176 MISSING="${MISSING} golang(${GO_VERSION})"
177 fi177 fi
178 178
179 check_version "$(env git --version 2>/dev/null | sed -e 's/^.* \([0-9.\].*\).*$/\1/' -e 's/^\([0-9.\]*\).*/\1/g')" "${GIT_VERSION}"179 check_version "$(env git --version 2>/dev/null | sed -e 's/^.* \([0-9.\].*\).*$/\1/' -e 's/^\([0-9.\]*\).*/\1/g')" "${GIT_VERSION}"
@@ -185,13 +185,13 @@ check_deps() { · 5 unmodified lines
185 "Linux")185 "Linux")
186 check_version "$(env gcc --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${GCC_VERSION}"186 check_version "$(env gcc --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${GCC_VERSION}"
187 if [ $? -ge 2 ]; then187 if [ $? -ge 2 ]; then
188 MISSING="${MISSING} build-essential"188 MISSING="${MISSING} build-essential(${GCC_VERSION})"
189 fi189 fi
190 ;;190 ;;
191 "Darwin")191 "Darwin")
192 check_version "$(env gcc --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${CLANG_VERSION}"192 check_version "$(env gcc --version 2>/dev/null | awk '{print $4}' | head -1)" "${LLVM_VERSION}"
193 if [ $? -ge 2 ]; then193 if [ $? -ge 2 ]; then
194 MISSING="${MISSING} xcode-cli"194 MISSING="${MISSING} xcode-cli(${LLVM_VERSION})"
195 fi195 fi
196 ;;196 ;;
197 "*")197 "*")
@@ -200,7 +200,7 @@ check_deps() { · 2 unmodified lines
200 200
201 check_version "$(env yasm --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${YASM_VERSION}"201 check_version "$(env yasm --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${YASM_VERSION}"
202 if [ $? -ge 2 ]; then202 if [ $? -ge 2 ]; then
203 MISSING="${MISSING} yasm(1.2.0)"203 MISSING="${MISSING} yasm(${YASM_VERSION})"
204 fi204 fi
205 205
206}206}
controller-main.go+2−5
@@ -16,10 +16,7 @@ · 15 unmodified lines
16 16
17package main17package main
18 18
19import (19import "github.com/minio/cli"
20 "github.com/minio/cli"
21 "github.com/minio/minio/pkg/controller"
22)
23 20
24var controllerCmd = cli.Command{21var controllerCmd = cli.Command{
25 Name: "controller",22 Name: "controller",
@@ -43,6 +40,6 @@ func controllerMain(c *cli.Context) { · 17 unmodified lines
43 cli.ShowCommandHelpAndExit(c, "controller", 1)40 cli.ShowCommandHelpAndExit(c, "controller", 1)
44 }41 }
45 42
46 err := controller.Start()43 err := StartController()
47 errorIf(err.Trace(), "Failed to start minio controller.", nil)44 errorIf(err.Trace(), "Failed to start minio controller.", nil)
48}45}
controller-router.gorenamed from pkg/controller/router.go+5−6
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package controller17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
@@ -22,17 +22,16 @@ import ( · 1 unmodified line
22 router "github.com/gorilla/mux"22 router "github.com/gorilla/mux"
23 jsonrpc "github.com/gorilla/rpc/v2"23 jsonrpc "github.com/gorilla/rpc/v2"
24 "github.com/gorilla/rpc/v2/json"24 "github.com/gorilla/rpc/v2/json"
25 "github.com/minio/minio/pkg/controller/rpc"
26)25)
27 26
28// getRPCHandler rpc handler27// getRPCHandler rpc handler
29func getRPCHandler() http.Handler {28func getRPCHandler() http.Handler {
30 s := jsonrpc.NewServer()29 s := jsonrpc.NewServer()
31 s.RegisterCodec(json.NewCodec(), "application/json")30 s.RegisterCodec(json.NewCodec(), "application/json")
32 s.RegisterService(new(rpc.VersionService), "Version")31 s.RegisterService(new(VersionService), "Version")
33 s.RegisterService(new(rpc.DonutService), "Donut")32 s.RegisterService(new(DonutService), "Donut")
34 s.RegisterService(new(rpc.AuthService), "Auth")33 s.RegisterService(new(AuthService), "Auth")
35 s.RegisterService(new(rpc.ServerService), "Server")34 s.RegisterService(new(ServerService), "Server")
36 // Add new RPC services here35 // Add new RPC services here
37 return registerRPC(router.NewRouter(), s)36 return registerRPC(router.NewRouter(), s)
38}37}
controller-rpc-auth.gorenamed from pkg/controller/rpc/auth.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package rpc17package main
18 18
19import (19import (
20 "errors"20 "errors"
controller-rpc-donut.gorenamed from pkg/controller/rpc/donut.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package rpc17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
controller-rpc-server.gorenamed from pkg/controller/rpc/server.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package rpc17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
controller-rpc-version.gorenamed from pkg/controller/rpc/version.go+2−3
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package rpc17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
@@ -38,8 +38,7 @@ type VersionReply struct { · 17 unmodified lines
38// Get version38// Get version
39func (v *VersionService) Get(r *http.Request, args *VersionArgs, reply *VersionReply) error {39func (v *VersionService) Get(r *http.Request, args *VersionArgs, reply *VersionReply) error {
40 reply.Version = "0.0.1"40 reply.Version = "0.0.1"
41 //TODO: Better approach needed here to pass global states like version. --ab.41 reply.BuildDate = minioVersion
42 // reply.BuildDate = version.Version
43 reply.Architecture = runtime.GOARCH42 reply.Architecture = runtime.GOARCH
44 reply.OperatingSystem = runtime.GOOS43 reply.OperatingSystem = runtime.GOOS
45 return nil44 return nil
controller.gorenamed from pkg/controller/server.go+3−3
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package controller17package main
18 18
19import (19import (
20 "fmt"20 "fmt"
@@ -54,8 +54,8 @@ func getRPCServer(rpcHandler http.Handler) (*http.Server, *probe.Error) { · 33 unmodified lines
54 return httpServer, nil54 return httpServer, nil
55}55}
56 56
57// Start starts a controller57// StartController starts a minio controller
58func Start() *probe.Error {58func StartController() *probe.Error {
59 rpcServer, err := getRPCServer(getRPCHandler())59 rpcServer, err := getRPCServer(getRPCHandler())
60 if err != nil {60 if err != nil {
61 return err.Trace()61 return err.Trace()
controller_rpc_test.gorenamed from pkg/controller/rpc_test.go+59−64
@@ -14,31 +14,26 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package controller17package main
18 18
19import (19import (
20 "io/ioutil"20 "io/ioutil"
21 "net/http"21 "net/http"
22 "net/http/httptest"22 "net/http/httptest"
23 "os"23 "os"
24 "testing"
25 24
26 jsonrpc "github.com/gorilla/rpc/v2/json"25 "github.com/gorilla/rpc/v2/json"
27 "github.com/minio/minio/pkg/auth"26 "github.com/minio/minio/pkg/auth"
28 "github.com/minio/minio/pkg/controller/rpc"
29 . "gopkg.in/check.v1"27 . "gopkg.in/check.v1"
30)28)
31 29
32// Hook up gocheck into the "go test" runner.30type ControllerRPCSuite struct{}
33func Test(t *testing.T) { TestingT(t) }
34 31
35type MySuite struct{}32var _ = Suite(&ControllerRPCSuite{})
36
37var _ = Suite(&MySuite{})
38 33
39var testRPCServer *httptest.Server34var testRPCServer *httptest.Server
40 35
41func (s *MySuite) SetUpSuite(c *C) {36func (s *ControllerRPCSuite) SetUpSuite(c *C) {
42 root, err := ioutil.TempDir(os.TempDir(), "api-")37 root, err := ioutil.TempDir(os.TempDir(), "api-")
43 c.Assert(err, IsNil)38 c.Assert(err, IsNil)
44 auth.SetAuthConfigPath(root)39 auth.SetAuthConfigPath(root)
@@ -46,136 +41,136 @@ func (s *MySuite) SetUpSuite(c *C) { · 1 unmodified line
46 testRPCServer = httptest.NewServer(getRPCHandler())41 testRPCServer = httptest.NewServer(getRPCHandler())
47}42}
48 43
49func (s *MySuite) TearDownSuite(c *C) {44func (s *ControllerRPCSuite) TearDownSuite(c *C) {
50 testRPCServer.Close()45 testRPCServer.Close()
51}46}
52 47
53func (s *MySuite) TestMemStats(c *C) {48func (s *ControllerRPCSuite) TestMemStats(c *C) {
54 op := rpc.Operation{49 op := rpcOperation{
55 Method: "Server.MemStats",50 Method: "Server.MemStats",
56 Request: rpc.ServerArgs{},51 Request: ServerArgs{},
57 }52 }
58 req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)53 req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
59 c.Assert(err, IsNil)54 c.Assert(err, IsNil)
60 c.Assert(req.Get("Content-Type"), Equals, "application/json")55 c.Assert(req.Get("Content-Type"), Equals, "application/json")
61 resp, err := req.Do()56 resp, err := req.Do()
62 c.Assert(err, IsNil)57 c.Assert(err, IsNil)
63 c.Assert(resp.StatusCode, Equals, http.StatusOK)58 c.Assert(resp.StatusCode, Equals, http.StatusOK)
64 59
65 var reply rpc.MemStatsReply60 var reply MemStatsReply
66 c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)61 c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil)
67 resp.Body.Close()62 resp.Body.Close()
68 c.Assert(reply, Not(DeepEquals), rpc.MemStatsReply{})63 c.Assert(reply, Not(DeepEquals), MemStatsReply{})
69}64}
70 65
71func (s *MySuite) TestSysInfo(c *C) {66func (s *ControllerRPCSuite) TestSysInfo(c *C) {
72 op := rpc.Operation{67 op := rpcOperation{
73 Method: "Server.SysInfo",68 Method: "Server.SysInfo",
74 Request: rpc.ServerArgs{},69 Request: ServerArgs{},
75 }70 }
76 req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)71 req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
77 c.Assert(err, IsNil)72 c.Assert(err, IsNil)
78 c.Assert(req.Get("Content-Type"), Equals, "application/json")73 c.Assert(req.Get("Content-Type"), Equals, "application/json")
79 resp, err := req.Do()74 resp, err := req.Do()
80 c.Assert(err, IsNil)75 c.Assert(err, IsNil)
81 c.Assert(resp.StatusCode, Equals, http.StatusOK)76 c.Assert(resp.StatusCode, Equals, http.StatusOK)
82 77
83 var reply rpc.SysInfoReply78 var reply SysInfoReply
84 c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)79 c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil)
85 resp.Body.Close()80 resp.Body.Close()
86 c.Assert(reply, Not(DeepEquals), rpc.SysInfoReply{})81 c.Assert(reply, Not(DeepEquals), SysInfoReply{})
87}82}
88 83
89func (s *MySuite) TestServerList(c *C) {84func (s *ControllerRPCSuite) TestServerList(c *C) {
90 op := rpc.Operation{85 op := rpcOperation{
91 Method: "Server.List",86 Method: "Server.List",
92 Request: rpc.ServerArgs{},87 Request: ServerArgs{},
93 }88 }
94 req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)89 req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
95 c.Assert(err, IsNil)90 c.Assert(err, IsNil)
96 c.Assert(req.Get("Content-Type"), Equals, "application/json")91 c.Assert(req.Get("Content-Type"), Equals, "application/json")
97 resp, err := req.Do()92 resp, err := req.Do()
98 c.Assert(err, IsNil)93 c.Assert(err, IsNil)
99 c.Assert(resp.StatusCode, Equals, http.StatusOK)94 c.Assert(resp.StatusCode, Equals, http.StatusOK)
100 95
101 var reply rpc.ServerListReply96 var reply ServerListReply
102 c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)97 c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil)
103 resp.Body.Close()98 resp.Body.Close()
104 c.Assert(reply, Not(DeepEquals), rpc.ServerListReply{})99 c.Assert(reply, Not(DeepEquals), ServerListReply{})
105}100}
106 101
107func (s *MySuite) TestServerAdd(c *C) {102func (s *ControllerRPCSuite) TestServerAdd(c *C) {
108 op := rpc.Operation{103 op := rpcOperation{
109 Method: "Server.Add",104 Method: "Server.Add",
110 Request: rpc.ServerArgs{MinioServers: []rpc.MinioServer{}},105 Request: ServerArgs{MinioServers: []MinioServer{}},
111 }106 }
112 req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)107 req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
113 c.Assert(err, IsNil)108 c.Assert(err, IsNil)
114 c.Assert(req.Get("Content-Type"), Equals, "application/json")109 c.Assert(req.Get("Content-Type"), Equals, "application/json")
115 resp, err := req.Do()110 resp, err := req.Do()
116 c.Assert(err, IsNil)111 c.Assert(err, IsNil)
117 c.Assert(resp.StatusCode, Equals, http.StatusOK)112 c.Assert(resp.StatusCode, Equals, http.StatusOK)
118 113
119 var reply rpc.ServerAddReply114 var reply ServerAddReply
120 c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)115 c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil)
121 resp.Body.Close()116 resp.Body.Close()
122 c.Assert(reply, Not(DeepEquals), rpc.ServerAddReply{ServersAdded: []rpc.MinioServer{}})117 c.Assert(reply, Not(DeepEquals), ServerAddReply{ServersAdded: []MinioServer{}})
123}118}
124 119
125func (s *MySuite) TestAuth(c *C) {120func (s *ControllerRPCSuite) TestAuth(c *C) {
126 op := rpc.Operation{121 op := rpcOperation{
127 Method: "Auth.Generate",122 Method: "Auth.Generate",
128 Request: rpc.AuthArgs{User: "newuser"},123 Request: AuthArgs{User: "newuser"},
129 }124 }
130 req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)125 req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
131 c.Assert(err, IsNil)126 c.Assert(err, IsNil)
132 c.Assert(req.Get("Content-Type"), Equals, "application/json")127 c.Assert(req.Get("Content-Type"), Equals, "application/json")
133 resp, err := req.Do()128 resp, err := req.Do()
134 c.Assert(err, IsNil)129 c.Assert(err, IsNil)
135 c.Assert(resp.StatusCode, Equals, http.StatusOK)130 c.Assert(resp.StatusCode, Equals, http.StatusOK)
136 131
137 var reply rpc.AuthReply132 var reply AuthReply
138 c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)133 c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil)
139 resp.Body.Close()134 resp.Body.Close()
140 c.Assert(reply, Not(DeepEquals), rpc.AuthReply{})135 c.Assert(reply, Not(DeepEquals), AuthReply{})
141 c.Assert(len(reply.AccessKeyID), Equals, 20)136 c.Assert(len(reply.AccessKeyID), Equals, 20)
142 c.Assert(len(reply.SecretAccessKey), Equals, 40)137 c.Assert(len(reply.SecretAccessKey), Equals, 40)
143 c.Assert(len(reply.Name), Not(Equals), 0)138 c.Assert(len(reply.Name), Not(Equals), 0)
144 139
145 op = rpc.Operation{140 op = rpcOperation{
146 Method: "Auth.Fetch",141 Method: "Auth.Fetch",
147 Request: rpc.AuthArgs{User: "newuser"},142 Request: AuthArgs{User: "newuser"},
148 }143 }
149 req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)144 req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
150 c.Assert(err, IsNil)145 c.Assert(err, IsNil)
151 c.Assert(req.Get("Content-Type"), Equals, "application/json")146 c.Assert(req.Get("Content-Type"), Equals, "application/json")
152 resp, err = req.Do()147 resp, err = req.Do()
153 c.Assert(err, IsNil)148 c.Assert(err, IsNil)
154 c.Assert(resp.StatusCode, Equals, http.StatusOK)149 c.Assert(resp.StatusCode, Equals, http.StatusOK)
155 150
156 var newReply rpc.AuthReply151 var newReply AuthReply
157 c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &newReply), IsNil)152 c.Assert(json.DecodeClientResponse(resp.Body, &newReply), IsNil)
158 resp.Body.Close()153 resp.Body.Close()
159 c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{})154 c.Assert(newReply, Not(DeepEquals), AuthReply{})
160 c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID)155 c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID)
161 c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey)156 c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey)
162 c.Assert(len(reply.Name), Not(Equals), 0)157 c.Assert(len(reply.Name), Not(Equals), 0)
163 158
164 op = rpc.Operation{159 op = rpcOperation{
165 Method: "Auth.Reset",160 Method: "Auth.Reset",
166 Request: rpc.AuthArgs{User: "newuser"},161 Request: AuthArgs{User: "newuser"},
167 }162 }
168 req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)163 req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
169 c.Assert(err, IsNil)164 c.Assert(err, IsNil)
170 c.Assert(req.Get("Content-Type"), Equals, "application/json")165 c.Assert(req.Get("Content-Type"), Equals, "application/json")
171 resp, err = req.Do()166 resp, err = req.Do()
172 c.Assert(err, IsNil)167 c.Assert(err, IsNil)
173 c.Assert(resp.StatusCode, Equals, http.StatusOK)168 c.Assert(resp.StatusCode, Equals, http.StatusOK)
174 169
175 var resetReply rpc.AuthReply170 var resetReply AuthReply
176 c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &resetReply), IsNil)171 c.Assert(json.DecodeClientResponse(resp.Body, &resetReply), IsNil)
177 resp.Body.Close()172 resp.Body.Close()
178 c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{})173 c.Assert(newReply, Not(DeepEquals), AuthReply{})
179 c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID)174 c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID)
180 c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey)175 c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey)
181 c.Assert(len(reply.Name), Not(Equals), 0)176 c.Assert(len(reply.Name), Not(Equals), 0)
@@ -183,11 +178,11 @@ func (s *MySuite) TestAuth(c *C) { · 1 unmodified line
183 // these operations should fail178 // these operations should fail
184 179
185 /// generating access for existing user fails180 /// generating access for existing user fails
186 op = rpc.Operation{181 op = rpcOperation{
187 Method: "Auth.Generate",182 Method: "Auth.Generate",
188 Request: rpc.AuthArgs{User: "newuser"},183 Request: AuthArgs{User: "newuser"},
189 }184 }
190 req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)185 req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
191 c.Assert(err, IsNil)186 c.Assert(err, IsNil)
192 c.Assert(req.Get("Content-Type"), Equals, "application/json")187 c.Assert(req.Get("Content-Type"), Equals, "application/json")
193 resp, err = req.Do()188 resp, err = req.Do()
@@ -195,11 +190,11 @@ func (s *MySuite) TestAuth(c *C) { · 1 unmodified line
195 c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)190 c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
196 191
197 /// null user provided invalid192 /// null user provided invalid
198 op = rpc.Operation{193 op = rpcOperation{
199 Method: "Auth.Generate",194 Method: "Auth.Generate",
200 Request: rpc.AuthArgs{User: ""},195 Request: AuthArgs{User: ""},
201 }196 }
202 req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)197 req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
203 c.Assert(err, IsNil)198 c.Assert(err, IsNil)
204 c.Assert(req.Get("Content-Type"), Equals, "application/json")199 c.Assert(req.Get("Content-Type"), Equals, "application/json")
205 resp, err = req.Do()200 resp, err = req.Do()
logger_test.go+5−1
@@ -27,7 +27,11 @@ import ( · 26 unmodified lines
27 . "gopkg.in/check.v1"27 . "gopkg.in/check.v1"
28)28)
29 29
30func (s *TestSuite) TestLogger(c *C) {30type LoggerSuite struct{}
31
32var _ = Suite(&LoggerSuite{})
33
34func (s *LoggerSuite) TestLogger(c *C) {
31 var buffer bytes.Buffer35 var buffer bytes.Buffer
32 var fields logrus.Fields36 var fields logrus.Fields
33 log.Out = &buffer37 log.Out = &buffer
minio_test.go+2−11
@@ -1,5 +1,5 @@
1/*1/*
2 * Minio Cloud Storage (C) 2015 Minio, Inc.2 * Minio Cloud Storage, (C) 2015 Minio, Inc.
3 *3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.5 * you may not use this file except in compliance with the License.
@@ -22,14 +22,5 @@ import ( · 16 unmodified lines
22 . "gopkg.in/check.v1"22 . "gopkg.in/check.v1"
23)23)
24 24
25// Hook up gocheck into the "go test" runner.
25func Test(t *testing.T) { TestingT(t) }26func Test(t *testing.T) { TestingT(t) }
26
27type TestSuite struct{}
28
29var _ = Suite(&TestSuite{})
30
31func (s *TestSuite) SetUpSuite(c *C) {
32}
33
34func (s *TestSuite) TearDownSuite(c *C) {
35}
rpc-request.gorenamed from pkg/controller/rpc/request.go+11−11
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package rpc17package main
18 18
19import (19import (
20 "bytes"20 "bytes"
@@ -24,20 +24,20 @@ import ( · 3 unmodified lines
24 "github.com/minio/minio/pkg/probe"24 "github.com/minio/minio/pkg/probe"
25)25)
26 26
27// Operation RPC operation27// rpcOperation RPC operation
28type Operation struct {28type rpcOperation struct {
29 Method string29 Method string
30 Request interface{}30 Request interface{}
31}31}
32 32
33// Request rpc client request33// rpcRequest rpc client request
34type Request struct {34type rpcRequest struct {
35 req *http.Request35 req *http.Request
36 transport http.RoundTripper36 transport http.RoundTripper
37}37}
38 38
39// NewRequest initiate a new client RPC request39// newRPCRequest initiate a new client RPC request
40func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request, *probe.Error) {40func newRPCRequest(url string, op rpcOperation, transport http.RoundTripper) (*rpcRequest, *probe.Error) {
41 params, err := json.EncodeClientRequest(op.Method, op.Request)41 params, err := json.EncodeClientRequest(op.Method, op.Request)
42 if err != nil {42 if err != nil {
43 return nil, probe.NewError(err)43 return nil, probe.NewError(err)
@@ -46,7 +46,7 @@ func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request · 2 unmodified lines
46 if err != nil {46 if err != nil {
47 return nil, probe.NewError(err)47 return nil, probe.NewError(err)
48 }48 }
49 rpcReq := &Request{}49 rpcReq := &rpcRequest{}
50 rpcReq.req = req50 rpcReq.req = req
51 rpcReq.req.Header.Set("Content-Type", "application/json")51 rpcReq.req.Header.Set("Content-Type", "application/json")
52 if transport == nil {52 if transport == nil {
@@ -57,7 +57,7 @@ func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request · 4 unmodified lines
57}57}
58 58
59// Do - make a http connection59// Do - make a http connection
60func (r Request) Do() (*http.Response, *probe.Error) {60func (r rpcRequest) Do() (*http.Response, *probe.Error) {
61 resp, err := r.transport.RoundTrip(r.req)61 resp, err := r.transport.RoundTrip(r.req)
62 if err != nil {62 if err != nil {
63 if err, ok := probe.UnwrapError(err); ok {63 if err, ok := probe.UnwrapError(err); ok {
@@ -69,11 +69,11 @@ func (r Request) Do() (*http.Response, *probe.Error) { · 5 unmodified lines
69}69}
70 70
71// Get - get value of requested header71// Get - get value of requested header
72func (r Request) Get(key string) string {72func (r rpcRequest) Get(key string) string {
73 return r.req.Header.Get(key)73 return r.req.Header.Get(key)
74}74}
75 75
76// Set - set value of a header key76// Set - set value of a header key
77func (r *Request) Set(key, value string) {77func (r *rpcRequest) Set(key, value string) {
78 r.req.Header.Set(key, value)78 r.req.Header.Set(key, value)
79}79}
server-api-acl.gorenamed from pkg/server/api/acl.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import "net/http"19import "net/http"
20 20
server-api-bucket-handlers.gorenamed from pkg/server/api/bucket-handlers.go+14−14
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
@@ -24,7 +24,7 @@ import ( · 3 unmodified lines
24 "github.com/minio/minio/pkg/probe"24 "github.com/minio/minio/pkg/probe"
25)25)
26 26
27func (api Minio) isValidOp(w http.ResponseWriter, req *http.Request, acceptsContentType contentType) bool {27func (api MinioAPI) isValidOp(w http.ResponseWriter, req *http.Request, acceptsContentType contentType) bool {
28 vars := mux.Vars(req)28 vars := mux.Vars(req)
29 bucket := vars["bucket"]29 bucket := vars["bucket"]
30 30
@@ -67,10 +67,10 @@ func (api Minio) isValidOp(w http.ResponseWriter, req *http.Request, acceptsCont · 36 unmodified lines
67// using the Initiate Multipart Upload request, but has not yet been completed or aborted.67// using the Initiate Multipart Upload request, but has not yet been completed or aborted.
68// This operation returns at most 1,000 multipart uploads in the response.68// This operation returns at most 1,000 multipart uploads in the response.
69//69//
70func (api Minio) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) {70func (api MinioAPI) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) {
71 // Ticket master block71 // Ticket master block
72 {72 {
73 op := Operation{}73 op := APIOperation{}
74 op.ProceedCh = make(chan struct{})74 op.ProceedCh = make(chan struct{})
75 api.OP <- op75 api.OP <- op
76 // block until ticket master gives us a go76 // block until ticket master gives us a go
@@ -133,10 +133,10 @@ func (api Minio) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Re · 56 unmodified lines
133// of the objects in a bucket. You can use the request parameters as selection133// of the objects in a bucket. You can use the request parameters as selection
134// criteria to return a subset of the objects in a bucket.134// criteria to return a subset of the objects in a bucket.
135//135//
136func (api Minio) ListObjectsHandler(w http.ResponseWriter, req *http.Request) {136func (api MinioAPI) ListObjectsHandler(w http.ResponseWriter, req *http.Request) {
137 // Ticket master block137 // Ticket master block
138 {138 {
139 op := Operation{}139 op := APIOperation{}
140 op.ProceedCh = make(chan struct{})140 op.ProceedCh = make(chan struct{})
141 api.OP <- op141 api.OP <- op
142 // block until Ticket master gives us a go142 // block until Ticket master gives us a go
@@ -208,10 +208,10 @@ func (api Minio) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { · 65 unmodified lines
208// -----------208// -----------
209// This implementation of the GET operation returns a list of all buckets209// This implementation of the GET operation returns a list of all buckets
210// owned by the authenticated sender of the request.210// owned by the authenticated sender of the request.
211func (api Minio) ListBucketsHandler(w http.ResponseWriter, req *http.Request) {211func (api MinioAPI) ListBucketsHandler(w http.ResponseWriter, req *http.Request) {
212 // Ticket master block212 // Ticket master block
213 {213 {
214 op := Operation{}214 op := APIOperation{}
215 op.ProceedCh = make(chan struct{})215 op.ProceedCh = make(chan struct{})
216 api.OP <- op216 api.OP <- op
217 // block until Ticket master gives us a go217 // block until Ticket master gives us a go
@@ -260,10 +260,10 @@ func (api Minio) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { · 42 unmodified lines
260// PutBucketHandler - PUT Bucket260// PutBucketHandler - PUT Bucket
261// ----------261// ----------
262// This implementation of the PUT operation creates a new bucket for authenticated request262// This implementation of the PUT operation creates a new bucket for authenticated request
263func (api Minio) PutBucketHandler(w http.ResponseWriter, req *http.Request) {263func (api MinioAPI) PutBucketHandler(w http.ResponseWriter, req *http.Request) {
264 // Ticket master block264 // Ticket master block
265 {265 {
266 op := Operation{}266 op := APIOperation{}
267 op.ProceedCh = make(chan struct{})267 op.ProceedCh = make(chan struct{})
268 api.OP <- op268 api.OP <- op
269 // block until Ticket master gives us a go269 // block until Ticket master gives us a go
@@ -338,10 +338,10 @@ func (api Minio) PutBucketHandler(w http.ResponseWriter, req *http.Request) { · 68 unmodified lines
338// PutBucketACLHandler - PUT Bucket ACL338// PutBucketACLHandler - PUT Bucket ACL
339// ----------339// ----------
340// This implementation of the PUT operation modifies the bucketACL for authenticated request340// This implementation of the PUT operation modifies the bucketACL for authenticated request
341func (api Minio) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) {341func (api MinioAPI) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) {
342 // Ticket master block342 // Ticket master block
343 {343 {
344 op := Operation{}344 op := APIOperation{}
345 op.ProceedCh = make(chan struct{})345 op.ProceedCh = make(chan struct{})
346 api.OP <- op346 api.OP <- op
347 // block until Ticket master gives us a go347 // block until Ticket master gives us a go
@@ -395,10 +395,10 @@ func (api Minio) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) { · 47 unmodified lines
395// The operation returns a 200 OK if the bucket exists and you395// The operation returns a 200 OK if the bucket exists and you
396// have permission to access it. Otherwise, the operation might396// have permission to access it. Otherwise, the operation might
397// return responses such as 404 Not Found and 403 Forbidden.397// return responses such as 404 Not Found and 403 Forbidden.
398func (api Minio) HeadBucketHandler(w http.ResponseWriter, req *http.Request) {398func (api MinioAPI) HeadBucketHandler(w http.ResponseWriter, req *http.Request) {
399 // Ticket master block399 // Ticket master block
400 {400 {
401 op := Operation{}401 op := APIOperation{}
402 op.ProceedCh = make(chan struct{})402 op.ProceedCh = make(chan struct{})
403 api.OP <- op403 api.OP <- op
404 // block until Ticket master gives us a go404 // block until Ticket master gives us a go
server-api-contenttype.gorenamed from pkg/server/api/contenttype.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import "net/http"19import "net/http"
20 20
server-api-definitions.gorenamed from pkg/server/api/definitions.go+3−3
@@ -14,12 +14,12 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import "encoding/xml"19import "encoding/xml"
20 20
21// Config - http server config21// APIConfig - http server config
22type Config struct {22type APIConfig struct {
23 Address string23 Address string
24 TLS bool24 TLS bool
25 CertFile string25 CertFile string
server-api-errors.gorenamed from pkg/server/api/errors.go+10−10
@@ -14,22 +14,22 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "encoding/xml"20 "encoding/xml"
21 "net/http"21 "net/http"
22)22)
23 23
24// Error structure24// APIError structure
25type Error struct {25type APIError struct {
26 Code string26 Code string
27 Description string27 Description string
28 HTTPStatusCode int28 HTTPStatusCode int
29}29}
30 30
31// ErrorResponse - error response format31// APIErrorResponse - error response format
32type ErrorResponse struct {32type APIErrorResponse struct {
33 XMLName xml.Name `xml:"Error" json:"-"`33 XMLName xml.Name `xml:"Error" json:"-"`
34 Code string34 Code string
35 Message string35 Message string
@@ -77,8 +77,8 @@ const ( · 41 unmodified lines
77 NotAcceptable = iota + 3077 NotAcceptable = iota + 30
78)78)
79 79
80// Error code to Error structure map80// APIError code to Error structure map
81var errorCodeResponse = map[int]Error{81var errorCodeResponse = map[int]APIError{
82 InvalidMaxUploads: {82 InvalidMaxUploads: {
83 Code: "InvalidArgument",83 Code: "InvalidArgument",
84 Description: "Argument maxUploads must be an integer between 0 and 2147483647.",84 Description: "Argument maxUploads must be an integer between 0 and 2147483647.",
@@ -232,14 +232,14 @@ var errorCodeResponse = map[int]Error{ · 147 unmodified lines
232}232}
233 233
234// errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown234// errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown
235func getErrorCode(code int) Error {235func getErrorCode(code int) APIError {
236 return errorCodeResponse[code]236 return errorCodeResponse[code]
237}237}
238 238
239// getErrorResponse gets in standard error and resource value and239// getErrorResponse gets in standard error and resource value and
240// provides a encodable populated response values240// provides a encodable populated response values
241func getErrorResponse(err Error, resource string) ErrorResponse {241func getErrorResponse(err APIError, resource string) APIErrorResponse {
242 var data = ErrorResponse{}242 var data = APIErrorResponse{}
243 data.Code = err.Code243 data.Code = err.Code
244 data.Message = err.Description244 data.Message = err.Description
245 if resource != "" {245 if resource != "" {
server-api-generic-handlers.gorenamed from pkg/server/api/generic-handlers.go+1−5
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "errors"20 "errors"
@@ -44,10 +44,6 @@ type resourceHandler struct { · 23 unmodified lines
44 handler http.Handler44 handler http.Handler
45}45}
46 46
47const (
48 iso8601Format = "20060102T150405Z"
49)
50
51func parseDate(req *http.Request) (time.Time, error) {47func parseDate(req *http.Request) (time.Time, error) {
52 amzDate := req.Header.Get(http.CanonicalHeaderKey("x-amz-date"))48 amzDate := req.Header.Get(http.CanonicalHeaderKey("x-amz-date"))
53 switch {49 switch {
server-api-headers.gorenamed from pkg/server/api/headers.go+3−4
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "bytes"20 "bytes"
@@ -22,6 +22,7 @@ import ( · 1 unmodified line
22 "encoding/json"22 "encoding/json"
23 "encoding/xml"23 "encoding/xml"
24 "net/http"24 "net/http"
25 "runtime"
25 "strconv"26 "strconv"
26 27
27 "github.com/minio/minio/pkg/donut"28 "github.com/minio/minio/pkg/donut"
@@ -51,9 +52,7 @@ func generateRequestID() []byte { · 23 unmodified lines
51func setCommonHeaders(w http.ResponseWriter, acceptsType string, contentLength int) {52func setCommonHeaders(w http.ResponseWriter, acceptsType string, contentLength int) {
52 // set unique request ID for each reply53 // set unique request ID for each reply
53 w.Header().Set("X-Amz-Request-Id", string(generateRequestID()))54 w.Header().Set("X-Amz-Request-Id", string(generateRequestID()))
54 55 w.Header().Set("Server", ("Minio/" + minioReleaseTag + " (" + runtime.GOOS + ";" + runtime.GOARCH + ")"))
55 // TODO: Modularity comes in the way of passing global state like "version". A better approach needed here. -ab
56 // w.Header().Set("Server", ("Minio/" + version + " (" + runtime.GOOS + ";" + runtime.GOARCH + ")"))
57 56
58 w.Header().Set("Accept-Ranges", "bytes")57 w.Header().Set("Accept-Ranges", "bytes")
59 w.Header().Set("Content-Type", acceptsType)58 w.Header().Set("Content-Type", acceptsType)
server-api-httprange.gorenamed from pkg/server/api/range.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "errors"20 "errors"
server-api-logging-handlers.gorenamed from pkg/server/api/logging-handlers.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "bytes"20 "bytes"
server-api-object-handlers.gorenamed from pkg/server/api/object-handlers.go+19−19
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
@@ -33,10 +33,10 @@ const ( · 12 unmodified lines
33// ----------33// ----------
34// This implementation of the GET operation retrieves object. To use GET,34// This implementation of the GET operation retrieves object. To use GET,
35// you must have READ access to the object.35// you must have READ access to the object.
36func (api Minio) GetObjectHandler(w http.ResponseWriter, req *http.Request) {36func (api MinioAPI) GetObjectHandler(w http.ResponseWriter, req *http.Request) {
37 // ticket master block37 // ticket master block
38 {38 {
39 op := Operation{}39 op := APIOperation{}
40 op.ProceedCh = make(chan struct{})40 op.ProceedCh = make(chan struct{})
41 api.OP <- op41 api.OP <- op
42 // block until Ticket master gives us a go42 // block until Ticket master gives us a go
@@ -100,10 +100,10 @@ func (api Minio) GetObjectHandler(w http.ResponseWriter, req *http.Request) { · 57 unmodified lines
100// HeadObjectHandler - HEAD Object100// HeadObjectHandler - HEAD Object
101// -----------101// -----------
102// The HEAD operation retrieves metadata from an object without returning the object itself.102// The HEAD operation retrieves metadata from an object without returning the object itself.
103func (api Minio) HeadObjectHandler(w http.ResponseWriter, req *http.Request) {103func (api MinioAPI) HeadObjectHandler(w http.ResponseWriter, req *http.Request) {
104 // ticket master block104 // ticket master block
105 {105 {
106 op := Operation{}106 op := APIOperation{}
107 op.ProceedCh = make(chan struct{})107 op.ProceedCh = make(chan struct{})
108 api.OP <- op108 api.OP <- op
109 // block until Ticket master gives us a go109 // block until Ticket master gives us a go
@@ -157,10 +157,10 @@ func (api Minio) HeadObjectHandler(w http.ResponseWriter, req *http.Request) { · 47 unmodified lines
157// PutObjectHandler - PUT Object157// PutObjectHandler - PUT Object
158// ----------158// ----------
159// This implementation of the PUT operation adds an object to a bucket.159// This implementation of the PUT operation adds an object to a bucket.
160func (api Minio) PutObjectHandler(w http.ResponseWriter, req *http.Request) {160func (api MinioAPI) PutObjectHandler(w http.ResponseWriter, req *http.Request) {
161 // Ticket master block161 // Ticket master block
162 {162 {
163 op := Operation{}163 op := APIOperation{}
164 op.ProceedCh = make(chan struct{})164 op.ProceedCh = make(chan struct{})
165 api.OP <- op165 api.OP <- op
166 // block until Ticket master gives us a go166 // block until Ticket master gives us a go
@@ -259,10 +259,10 @@ func (api Minio) PutObjectHandler(w http.ResponseWriter, req *http.Request) { · 92 unmodified lines
259/// Multipart API259/// Multipart API
260 260
261// NewMultipartUploadHandler - New multipart upload261// NewMultipartUploadHandler - New multipart upload
262func (api Minio) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {262func (api MinioAPI) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {
263 // Ticket master block263 // Ticket master block
264 {264 {
265 op := Operation{}265 op := APIOperation{}
266 op.ProceedCh = make(chan struct{})266 op.ProceedCh = make(chan struct{})
267 api.OP <- op267 api.OP <- op
268 // block until Ticket master gives us a go268 // block until Ticket master gives us a go
@@ -317,10 +317,10 @@ func (api Minio) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Requ · 48 unmodified lines
317}317}
318 318
319// PutObjectPartHandler - Upload part319// PutObjectPartHandler - Upload part
320func (api Minio) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) {320func (api MinioAPI) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) {
321 // Ticket master block321 // Ticket master block
322 {322 {
323 op := Operation{}323 op := APIOperation{}
324 op.ProceedCh = make(chan struct{})324 op.ProceedCh = make(chan struct{})
325 api.OP <- op325 api.OP <- op
326 // block until Ticket master gives us a go326 // block until Ticket master gives us a go
@@ -417,10 +417,10 @@ func (api Minio) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) · 90 unmodified lines
417}417}
418 418
419// AbortMultipartUploadHandler - Abort multipart upload419// AbortMultipartUploadHandler - Abort multipart upload
420func (api Minio) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {420func (api MinioAPI) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {
421 // Ticket master block421 // Ticket master block
422 {422 {
423 op := Operation{}423 op := APIOperation{}
424 op.ProceedCh = make(chan struct{})424 op.ProceedCh = make(chan struct{})
425 api.OP <- op425 api.OP <- op
426 // block until Ticket master gives us a go426 // block until Ticket master gives us a go
@@ -467,10 +467,10 @@ func (api Minio) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Re · 40 unmodified lines
467}467}
468 468
469// ListObjectPartsHandler - List object parts469// ListObjectPartsHandler - List object parts
470func (api Minio) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request) {470func (api MinioAPI) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request) {
471 // Ticket master block471 // Ticket master block
472 {472 {
473 op := Operation{}473 op := APIOperation{}
474 op.ProceedCh = make(chan struct{})474 op.ProceedCh = make(chan struct{})
475 api.OP <- op475 api.OP <- op
476 // block until Ticket master gives us a go476 // block until Ticket master gives us a go
@@ -532,10 +532,10 @@ func (api Minio) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request · 55 unmodified lines
532}532}
533 533
534// CompleteMultipartUploadHandler - Complete multipart upload534// CompleteMultipartUploadHandler - Complete multipart upload
535func (api Minio) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {535func (api MinioAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {
536 // Ticket master block536 // Ticket master block
537 {537 {
538 op := Operation{}538 op := APIOperation{}
539 op.ProceedCh = make(chan struct{})539 op.ProceedCh = make(chan struct{})
540 api.OP <- op540 api.OP <- op
541 // block until Ticket master gives us a go541 // block until Ticket master gives us a go
@@ -597,13 +597,13 @@ func (api Minio) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http · 55 unmodified lines
597/// Delete API597/// Delete API
598 598
599// DeleteBucketHandler - Delete bucket599// DeleteBucketHandler - Delete bucket
600func (api Minio) DeleteBucketHandler(w http.ResponseWriter, req *http.Request) {600func (api MinioAPI) DeleteBucketHandler(w http.ResponseWriter, req *http.Request) {
601 error := getErrorCode(MethodNotAllowed)601 error := getErrorCode(MethodNotAllowed)
602 w.WriteHeader(error.HTTPStatusCode)602 w.WriteHeader(error.HTTPStatusCode)
603}603}
604 604
605// DeleteObjectHandler - Delete object605// DeleteObjectHandler - Delete object
606func (api Minio) DeleteObjectHandler(w http.ResponseWriter, req *http.Request) {606func (api MinioAPI) DeleteObjectHandler(w http.ResponseWriter, req *http.Request) {
607 error := getErrorCode(MethodNotAllowed)607 error := getErrorCode(MethodNotAllowed)
608 w.WriteHeader(error.HTTPStatusCode)608 w.WriteHeader(error.HTTPStatusCode)
609}609}
server-api-resources.gorenamed from pkg/server/api/resources.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "net/url"20 "net/url"
server-api-response.gorenamed from pkg/server/api/response.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
server-api-signature.gorenamed from pkg/server/api/signature.go+3−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "errors"20 "errors"
@@ -28,6 +28,8 @@ import ( · 7 unmodified lines
28 28
29const (29const (
30 authHeaderPrefix = "AWS4-HMAC-SHA256"30 authHeaderPrefix = "AWS4-HMAC-SHA256"
31 iso8601Format = "20060102T150405Z"
32 yyyymmdd = "20060102"
31)33)
32 34
33// getCredentialsFromAuth parse credentials tag from authorization value35// getCredentialsFromAuth parse credentials tag from authorization value
server-api-typed-errors.gorenamed from pkg/server/api/typed-errors.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import "errors"19import "errors"
20 20
server-api-utils.gorenamed from pkg/server/api/utils.go+1−1
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import (19import (
20 "encoding/base64"20 "encoding/base64"
server-api.gorenamed from pkg/server/api/api.go+10−10
@@ -14,30 +14,30 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package api17package main
18 18
19import "github.com/minio/minio/pkg/donut"19import "github.com/minio/minio/pkg/donut"
20 20
21// Operation container for individual operations read by Ticket Master21// APIOperation container for individual operations read by Ticket Master
22type Operation struct {22type APIOperation struct {
23 ProceedCh chan struct{}23 ProceedCh chan struct{}
24}24}
25 25
26// Minio container for API and also carries OP (operation) channel26// MinioAPI container for API and also carries OP (operation) channel
27type Minio struct {27type MinioAPI struct {
28 OP chan Operation28 OP chan APIOperation
29 Donut donut.Interface29 Donut donut.Interface
30}30}
31 31
32// New instantiate a new minio API32// NewAPI instantiate a new minio API
33func New() Minio {33func NewAPI() MinioAPI {
34 // ignore errors for now34 // ignore errors for now
35 d, err := donut.New()35 d, err := donut.New()
36 if err != nil {36 if err != nil {
37 panic(err)37 panic(err)
38 }38 }
39 return Minio{39 return MinioAPI{
40 OP: make(chan Operation),40 OP: make(chan APIOperation),
41 Donut: d,41 Donut: d,
42 }42 }
43}43}
server-main.go+4−8
@@ -16,11 +16,7 @@ · 15 unmodified lines
16 16
17package main17package main
18 18
19import (19import "github.com/minio/cli"
20 "github.com/minio/cli"
21 "github.com/minio/minio/pkg/server"
22 "github.com/minio/minio/pkg/server/api"
23)
24 20
25var serverCmd = cli.Command{21var serverCmd = cli.Command{
26 Name: "server",22 Name: "server",
@@ -39,14 +35,14 @@ EXAMPLES: · 12 unmodified lines
39`,35`,
40}36}
41 37
42func getServerConfig(c *cli.Context) api.Config {38func getServerConfig(c *cli.Context) APIConfig {
43 certFile := c.GlobalString("cert")39 certFile := c.GlobalString("cert")
44 keyFile := c.GlobalString("key")40 keyFile := c.GlobalString("key")
45 if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") {41 if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") {
46 Fatalln("Both certificate and key are required to enable https.")42 Fatalln("Both certificate and key are required to enable https.")
47 }43 }
48 tls := (certFile != "" && keyFile != "")44 tls := (certFile != "" && keyFile != "")
49 return api.Config{45 return APIConfig{
50 Address: c.GlobalString("address"),46 Address: c.GlobalString("address"),
51 TLS: tls,47 TLS: tls,
52 CertFile: certFile,48 CertFile: certFile,
@@ -61,6 +57,6 @@ func serverMain(c *cli.Context) { · 8 unmodified lines
61 }57 }
62 58
63 apiServerConfig := getServerConfig(c)59 apiServerConfig := getServerConfig(c)
64 err := server.Start(apiServerConfig)60 err := StartServer(apiServerConfig)
65 errorIf(err.Trace(), "Failed to start the minio server.", nil)61 errorIf(err.Trace(), "Failed to start the minio server.", nil)
66}62}
server-router.gorenamed from pkg/server/router.go+11−12
@@ -14,17 +14,16 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package server17package main
18 18
19import (19import (
20 "net/http"20 "net/http"
21 21
22 router "github.com/gorilla/mux"22 router "github.com/gorilla/mux"
23 "github.com/minio/minio/pkg/server/api"
24)23)
25 24
26// registerAPI - register all the object API handlers to their respective paths25// registerAPI - register all the object API handlers to their respective paths
27func registerAPI(mux *router.Router, a api.Minio) {26func registerAPI(mux *router.Router, a MinioAPI) {
28 mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET")27 mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET")
29 mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET")28 mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET")
30 mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT")29 mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT")
@@ -45,7 +44,7 @@ func registerAPI(mux *router.Router, a api.Minio) { · 14 unmodified lines
45 mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE")44 mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE")
46}45}
47 46
48func registerCustomMiddleware(mux *router.Router, mwHandlers ...api.MiddlewareHandler) http.Handler {47func registerCustomMiddleware(mux *router.Router, mwHandlers ...MiddlewareHandler) http.Handler {
49 var f http.Handler48 var f http.Handler
50 f = mux49 f = mux
51 for _, mw := range mwHandlers {50 for _, mw := range mwHandlers {
@@ -55,18 +54,18 @@ func registerCustomMiddleware(mux *router.Router, mwHandlers ...api.MiddlewareHa · 3 unmodified lines
55}54}
56 55
57// getAPIHandler api handler56// getAPIHandler api handler
58func getAPIHandler(conf api.Config) (http.Handler, api.Minio) {57func getAPIHandler(conf APIConfig) (http.Handler, MinioAPI) {
59 var mwHandlers = []api.MiddlewareHandler{58 var mwHandlers = []MiddlewareHandler{
60 api.ValidContentTypeHandler,59 ValidContentTypeHandler,
61 api.TimeValidityHandler,60 TimeValidityHandler,
62 api.IgnoreResourcesHandler,61 IgnoreResourcesHandler,
63 api.ValidateAuthHeaderHandler,62 ValidateAuthHeaderHandler,
64 // api.LoggingHandler, // Disabled logging until we bring in external logging support63 // api.LoggingHandler, // Disabled logging until we bring in external logging support
65 api.CorsHandler,64 CorsHandler,
66 }65 }
67 66
68 mux := router.NewRouter()67 mux := router.NewRouter()
69 minioAPI := api.New()68 minioAPI := NewAPI()
70 registerAPI(mux, minioAPI)69 registerAPI(mux, minioAPI)
71 apiHandler := registerCustomMiddleware(mux, mwHandlers...)70 apiHandler := registerCustomMiddleware(mux, mwHandlers...)
72 return apiHandler, minioAPI71 return apiHandler, minioAPI
server-signature-v4_test.gorenamed from pkg/server/signature-v4_test.go+3−15
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package server17package main
18 18
19import (19import (
20 "bytes"20 "bytes"
@@ -28,20 +28,8 @@ import ( · 7 unmodified lines
28 "regexp"28 "regexp"
29 "sort"29 "sort"
30 "strings"30 "strings"
31 "testing"
32 "time"31 "time"
33 "unicode/utf8"32 "unicode/utf8"
34
35 . "gopkg.in/check.v1"
36)
37
38// Hook up gocheck into the "go test" runner.
39func Test(t *testing.T) { TestingT(t) }
40
41const (
42 authHeader = "AWS4-HMAC-SHA256"
43 iso8601Format = "20060102T150405Z"
44 yyyymmdd = "20060102"
45)33)
46 34
47///35///
@@ -254,7 +242,7 @@ func (s *MyAPISignatureV4Suite) newRequest(method, urlStr string, contentLength · 206 unmodified lines
254 "aws4_request",242 "aws4_request",
255 }, "/")243 }, "/")
256 244
257 stringToSign := authHeader + "\n" + t.Format(iso8601Format) + "\n"245 stringToSign := authHeaderPrefix + "\n" + t.Format(iso8601Format) + "\n"
258 stringToSign = stringToSign + scope + "\n"246 stringToSign = stringToSign + scope + "\n"
259 stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest)))247 stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest)))
260 248
@@ -267,7 +255,7 @@ func (s *MyAPISignatureV4Suite) newRequest(method, urlStr string, contentLength · 6 unmodified lines
267 255
268 // final Authorization header256 // final Authorization header
269 parts := []string{257 parts := []string{
270 authHeader + " Credential=" + s.accessKeyID + "/" + scope,258 authHeaderPrefix + " Credential=" + s.accessKeyID + "/" + scope,
271 "SignedHeaders=" + signedHeaders,259 "SignedHeaders=" + signedHeaders,
272 "Signature=" + signature,260 "Signature=" + signature,
273 }261 }
server.gorenamed from pkg/server/server.go+5−6
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package server17package main
18 18
19import (19import (
20 "crypto/tls"20 "crypto/tls"
@@ -26,11 +26,10 @@ import ( · 5 unmodified lines
26 26
27 "github.com/minio/minio/pkg/minhttp"27 "github.com/minio/minio/pkg/minhttp"
28 "github.com/minio/minio/pkg/probe"28 "github.com/minio/minio/pkg/probe"
29 "github.com/minio/minio/pkg/server/api"
30)29)
31 30
32// getAPI server instance31// getAPI server instance
33func getAPIServer(conf api.Config, apiHandler http.Handler) (*http.Server, *probe.Error) {32func getAPIServer(conf APIConfig, apiHandler http.Handler) (*http.Server, *probe.Error) {
34 // Minio server config33 // Minio server config
35 httpServer := &http.Server{34 httpServer := &http.Server{
36 Addr: conf.Address,35 Addr: conf.Address,
@@ -84,7 +83,7 @@ func getAPIServer(conf api.Config, apiHandler http.Handler) (*http.Server, *prob · 47 unmodified lines
84}83}
85 84
86// Start ticket master85// Start ticket master
87func startTM(a api.Minio) {86func startTM(a MinioAPI) {
88 for {87 for {
89 for op := range a.OP {88 for op := range a.OP {
90 op.ProceedCh <- struct{}{}89 op.ProceedCh <- struct{}{}
@@ -92,8 +91,8 @@ func startTM(a api.Minio) { · 1 unmodified line
92 }91 }
93}92}
94 93
95// Start starts a s3 compatible cloud storage server94// StartServer starts an s3 compatible cloud storage server
96func Start(conf api.Config) *probe.Error {95func StartServer(conf APIConfig) *probe.Error {
97 apiHandler, minioAPI := getAPIHandler(conf)96 apiHandler, minioAPI := getAPIHandler(conf)
98 apiServer, err := getAPIServer(conf, apiHandler)97 apiServer, err := getAPIServer(conf, apiHandler)
99 if err != nil {98 if err != nil {
server_donut_cache_test.gorenamed from pkg/server/api_donut_cache_test.go+9−10
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package server17package main
18 18
19import (19import (
20 "bytes"20 "bytes"
@@ -28,7 +28,6 @@ import ( · 7 unmodified lines
28 "net/http/httptest"28 "net/http/httptest"
29 29
30 "github.com/minio/minio/pkg/donut"30 "github.com/minio/minio/pkg/donut"
31 "github.com/minio/minio/pkg/server/api"
32 . "gopkg.in/check.v1"31 . "gopkg.in/check.v1"
33)32)
34 33
@@ -52,7 +51,7 @@ func (s *MyAPIDonutCacheSuite) SetUpSuite(c *C) { · 17 unmodified lines
52 perr := donut.SaveConfig(conf)51 perr := donut.SaveConfig(conf)
53 c.Assert(perr, IsNil)52 c.Assert(perr, IsNil)
54 53
55 httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16})54 httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16})
56 go startTM(minioAPI)55 go startTM(minioAPI)
57 testAPIDonutCacheServer = httptest.NewServer(httpHandler)56 testAPIDonutCacheServer = httptest.NewServer(httpHandler)
58}57}
@@ -319,7 +318,7 @@ func (s *MyAPIDonutCacheSuite) TestListBuckets(c *C) { · 260 unmodified lines
319 c.Assert(err, IsNil)318 c.Assert(err, IsNil)
320 c.Assert(response.StatusCode, Equals, http.StatusOK)319 c.Assert(response.StatusCode, Equals, http.StatusOK)
321 320
322 var results api.ListBucketsResponse321 var results ListBucketsResponse
323 decoder := xml.NewDecoder(response.Body)322 decoder := xml.NewDecoder(response.Body)
324 err = decoder.Decode(&results)323 err = decoder.Decode(&results)
325 c.Assert(err, IsNil)324 c.Assert(err, IsNil)
@@ -676,7 +675,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartAbort(c *C) { · 350 unmodified lines
676 c.Assert(response.StatusCode, Equals, http.StatusOK)675 c.Assert(response.StatusCode, Equals, http.StatusOK)
677 676
678 decoder := xml.NewDecoder(response.Body)677 decoder := xml.NewDecoder(response.Body)
679 newResponse := &api.InitiateMultipartUploadResponse{}678 newResponse := &InitiateMultipartUploadResponse{}
680 679
681 err = decoder.Decode(newResponse)680 err = decoder.Decode(newResponse)
682 c.Assert(err, IsNil)681 c.Assert(err, IsNil)
@@ -721,7 +720,7 @@ func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) { · 38 unmodified lines
721 c.Assert(response.StatusCode, Equals, http.StatusOK)720 c.Assert(response.StatusCode, Equals, http.StatusOK)
722 721
723 decoder := xml.NewDecoder(response.Body)722 decoder := xml.NewDecoder(response.Body)
724 newResponse := &api.InitiateMultipartUploadResponse{}723 newResponse := &InitiateMultipartUploadResponse{}
725 724
726 err = decoder.Decode(newResponse)725 err = decoder.Decode(newResponse)
727 c.Assert(err, IsNil)726 c.Assert(err, IsNil)
@@ -750,7 +749,7 @@ func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) { · 22 unmodified lines
750 c.Assert(response3.StatusCode, Equals, http.StatusOK)749 c.Assert(response3.StatusCode, Equals, http.StatusOK)
751 750
752 decoder = xml.NewDecoder(response3.Body)751 decoder = xml.NewDecoder(response3.Body)
753 newResponse3 := &api.ListMultipartUploadsResponse{}752 newResponse3 := &ListMultipartUploadsResponse{}
754 err = decoder.Decode(newResponse3)753 err = decoder.Decode(newResponse3)
755 c.Assert(err, IsNil)754 c.Assert(err, IsNil)
756 c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")755 c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")
@@ -772,7 +771,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartList(c *C) { · 15 unmodified lines
772 c.Assert(response.StatusCode, Equals, http.StatusOK)771 c.Assert(response.StatusCode, Equals, http.StatusOK)
773 772
774 decoder := xml.NewDecoder(response.Body)773 decoder := xml.NewDecoder(response.Body)
775 newResponse := &api.InitiateMultipartUploadResponse{}774 newResponse := &InitiateMultipartUploadResponse{}
776 775
777 err = decoder.Decode(newResponse)776 err = decoder.Decode(newResponse)
778 c.Assert(err, IsNil)777 c.Assert(err, IsNil)
@@ -826,7 +825,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) { · 47 unmodified lines
826 c.Assert(response.StatusCode, Equals, http.StatusOK)825 c.Assert(response.StatusCode, Equals, http.StatusOK)
827 826
828 decoder := xml.NewDecoder(response.Body)827 decoder := xml.NewDecoder(response.Body)
829 newResponse := &api.InitiateMultipartUploadResponse{}828 newResponse := &InitiateMultipartUploadResponse{}
830 829
831 err = decoder.Decode(newResponse)830 err = decoder.Decode(newResponse)
832 c.Assert(err, IsNil)831 c.Assert(err, IsNil)
@@ -888,7 +887,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) { · 55 unmodified lines
888func verifyError(c *C, response *http.Response, code, description string, statusCode int) {887func verifyError(c *C, response *http.Response, code, description string, statusCode int) {
889 data, err := ioutil.ReadAll(response.Body)888 data, err := ioutil.ReadAll(response.Body)
890 c.Assert(err, IsNil)889 c.Assert(err, IsNil)
891 errorResponse := api.ErrorResponse{}890 errorResponse := APIErrorResponse{}
892 err = xml.Unmarshal(data, &errorResponse)891 err = xml.Unmarshal(data, &errorResponse)
893 c.Assert(err, IsNil)892 c.Assert(err, IsNil)
894 c.Assert(errorResponse.Code, Equals, code)893 c.Assert(errorResponse.Code, Equals, code)
server_donut_test.gorenamed from pkg/server/api_donut_test.go+8−9
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package server17package main
18 18
19import (19import (
20 "bytes"20 "bytes"
@@ -29,7 +29,6 @@ import ( · 8 unmodified lines
29 "net/http/httptest"29 "net/http/httptest"
30 30
31 "github.com/minio/minio/pkg/donut"31 "github.com/minio/minio/pkg/donut"
32 "github.com/minio/minio/pkg/server/api"
33 . "gopkg.in/check.v1"32 . "gopkg.in/check.v1"
34)33)
35 34
@@ -71,7 +70,7 @@ func (s *MyAPIDonutSuite) SetUpSuite(c *C) { · 35 unmodified lines
71 perr := donut.SaveConfig(conf)70 perr := donut.SaveConfig(conf)
72 c.Assert(perr, IsNil)71 c.Assert(perr, IsNil)
73 72
74 httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16})73 httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16})
75 go startTM(minioAPI)74 go startTM(minioAPI)
76 testAPIDonutServer = httptest.NewServer(httpHandler)75 testAPIDonutServer = httptest.NewServer(httpHandler)
77}76}
@@ -338,7 +337,7 @@ func (s *MyAPIDonutSuite) TestListBuckets(c *C) { · 260 unmodified lines
338 c.Assert(err, IsNil)337 c.Assert(err, IsNil)
339 c.Assert(response.StatusCode, Equals, http.StatusOK)338 c.Assert(response.StatusCode, Equals, http.StatusOK)
340 339
341 var results api.ListBucketsResponse340 var results ListBucketsResponse
342 decoder := xml.NewDecoder(response.Body)341 decoder := xml.NewDecoder(response.Body)
343 err = decoder.Decode(&results)342 err = decoder.Decode(&results)
344 c.Assert(err, IsNil)343 c.Assert(err, IsNil)
@@ -696,7 +695,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartAbort(c *C) { · 351 unmodified lines
696 c.Assert(response.StatusCode, Equals, http.StatusOK)695 c.Assert(response.StatusCode, Equals, http.StatusOK)
697 696
698 decoder := xml.NewDecoder(response.Body)697 decoder := xml.NewDecoder(response.Body)
699 newResponse := &api.InitiateMultipartUploadResponse{}698 newResponse := &InitiateMultipartUploadResponse{}
700 699
701 err = decoder.Decode(newResponse)700 err = decoder.Decode(newResponse)
702 c.Assert(err, IsNil)701 c.Assert(err, IsNil)
@@ -741,7 +740,7 @@ func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) { · 38 unmodified lines
741 c.Assert(response.StatusCode, Equals, http.StatusOK)740 c.Assert(response.StatusCode, Equals, http.StatusOK)
742 741
743 decoder := xml.NewDecoder(response.Body)742 decoder := xml.NewDecoder(response.Body)
744 newResponse := &api.InitiateMultipartUploadResponse{}743 newResponse := &InitiateMultipartUploadResponse{}
745 744
746 err = decoder.Decode(newResponse)745 err = decoder.Decode(newResponse)
747 c.Assert(err, IsNil)746 c.Assert(err, IsNil)
@@ -770,7 +769,7 @@ func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) { · 22 unmodified lines
770 c.Assert(response3.StatusCode, Equals, http.StatusOK)769 c.Assert(response3.StatusCode, Equals, http.StatusOK)
771 770
772 decoder = xml.NewDecoder(response3.Body)771 decoder = xml.NewDecoder(response3.Body)
773 newResponse3 := &api.ListMultipartUploadsResponse{}772 newResponse3 := &ListMultipartUploadsResponse{}
774 err = decoder.Decode(newResponse3)773 err = decoder.Decode(newResponse3)
775 c.Assert(err, IsNil)774 c.Assert(err, IsNil)
776 c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")775 c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")
@@ -792,7 +791,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartList(c *C) { · 15 unmodified lines
792 c.Assert(response.StatusCode, Equals, http.StatusOK)791 c.Assert(response.StatusCode, Equals, http.StatusOK)
793 792
794 decoder := xml.NewDecoder(response.Body)793 decoder := xml.NewDecoder(response.Body)
795 newResponse := &api.InitiateMultipartUploadResponse{}794 newResponse := &InitiateMultipartUploadResponse{}
796 795
797 err = decoder.Decode(newResponse)796 err = decoder.Decode(newResponse)
798 c.Assert(err, IsNil)797 c.Assert(err, IsNil)
@@ -846,7 +845,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipart(c *C) { · 47 unmodified lines
846 c.Assert(response.StatusCode, Equals, http.StatusOK)845 c.Assert(response.StatusCode, Equals, http.StatusOK)
847 846
848 decoder := xml.NewDecoder(response.Body)847 decoder := xml.NewDecoder(response.Body)
849 newResponse := &api.InitiateMultipartUploadResponse{}848 newResponse := &InitiateMultipartUploadResponse{}
850 849
851 err = decoder.Decode(newResponse)850 err = decoder.Decode(newResponse)
852 c.Assert(err, IsNil)851 c.Assert(err, IsNil)
server_signature_v4_test.gorenamed from pkg/server/api_signature_v4_test.go+8−9
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 * limitations under the License.14 * limitations under the License.
15 */15 */
16 16
17package server17package main
18 18
19import (19import (
20 "bytes"20 "bytes"
@@ -30,7 +30,6 @@ import ( · 9 unmodified lines
30 30
31 "github.com/minio/minio/pkg/auth"31 "github.com/minio/minio/pkg/auth"
32 "github.com/minio/minio/pkg/donut"32 "github.com/minio/minio/pkg/donut"
33 "github.com/minio/minio/pkg/server/api"
34 . "gopkg.in/check.v1"33 . "gopkg.in/check.v1"
35)34)
36 35
@@ -79,7 +78,7 @@ func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) { · 42 unmodified lines
79 perr = auth.SaveConfig(authConf)78 perr = auth.SaveConfig(authConf)
80 c.Assert(perr, IsNil)79 c.Assert(perr, IsNil)
81 80
82 httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16})81 httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16})
83 go startTM(minioAPI)82 go startTM(minioAPI)
84 testSignatureV4Server = httptest.NewServer(httpHandler)83 testSignatureV4Server = httptest.NewServer(httpHandler)
85}84}
@@ -347,7 +346,7 @@ func (s *MyAPISignatureV4Suite) TestListBuckets(c *C) { · 261 unmodified lines
347 c.Assert(err, IsNil)346 c.Assert(err, IsNil)
348 c.Assert(response.StatusCode, Equals, http.StatusOK)347 c.Assert(response.StatusCode, Equals, http.StatusOK)
349 348
350 var results api.ListBucketsResponse349 var results ListBucketsResponse
351 decoder := xml.NewDecoder(response.Body)350 decoder := xml.NewDecoder(response.Body)
352 err = decoder.Decode(&results)351 err = decoder.Decode(&results)
353 c.Assert(err, IsNil)352 c.Assert(err, IsNil)
@@ -689,7 +688,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipartAbort(c *C) { · 335 unmodified lines
689 c.Assert(response.StatusCode, Equals, http.StatusOK)688 c.Assert(response.StatusCode, Equals, http.StatusOK)
690 689
691 decoder := xml.NewDecoder(response.Body)690 decoder := xml.NewDecoder(response.Body)
692 newResponse := &api.InitiateMultipartUploadResponse{}691 newResponse := &InitiateMultipartUploadResponse{}
693 692
694 err = decoder.Decode(newResponse)693 err = decoder.Decode(newResponse)
695 c.Assert(err, IsNil)694 c.Assert(err, IsNil)
@@ -736,7 +735,7 @@ func (s *MyAPISignatureV4Suite) TestBucketMultipartList(c *C) { · 40 unmodified lines
736 c.Assert(response.StatusCode, Equals, http.StatusOK)735 c.Assert(response.StatusCode, Equals, http.StatusOK)
737 736
738 decoder := xml.NewDecoder(response.Body)737 decoder := xml.NewDecoder(response.Body)
739 newResponse := &api.InitiateMultipartUploadResponse{}738 newResponse := &InitiateMultipartUploadResponse{}
740 739
741 err = decoder.Decode(newResponse)740 err = decoder.Decode(newResponse)
742 c.Assert(err, IsNil)741 c.Assert(err, IsNil)
@@ -767,7 +766,7 @@ func (s *MyAPISignatureV4Suite) TestBucketMultipartList(c *C) { · 24 unmodified lines
767 c.Assert(response3.StatusCode, Equals, http.StatusOK)766 c.Assert(response3.StatusCode, Equals, http.StatusOK)
768 767
769 decoder = xml.NewDecoder(response3.Body)768 decoder = xml.NewDecoder(response3.Body)
770 newResponse3 := &api.ListMultipartUploadsResponse{}769 newResponse3 := &ListMultipartUploadsResponse{}
771 err = decoder.Decode(newResponse3)770 err = decoder.Decode(newResponse3)
772 c.Assert(err, IsNil)771 c.Assert(err, IsNil)
773 c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")772 c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")
@@ -789,7 +788,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipartList(c *C) { · 15 unmodified lines
789 c.Assert(response.StatusCode, Equals, http.StatusOK)788 c.Assert(response.StatusCode, Equals, http.StatusOK)
790 789
791 decoder := xml.NewDecoder(response.Body)790 decoder := xml.NewDecoder(response.Body)
792 newResponse := &api.InitiateMultipartUploadResponse{}791 newResponse := &InitiateMultipartUploadResponse{}
793 792
794 err = decoder.Decode(newResponse)793 err = decoder.Decode(newResponse)
795 c.Assert(err, IsNil)794 c.Assert(err, IsNil)
@@ -845,7 +844,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipart(c *C) { · 49 unmodified lines
845 c.Assert(response.StatusCode, Equals, http.StatusOK)844 c.Assert(response.StatusCode, Equals, http.StatusOK)
846 845
847 decoder := xml.NewDecoder(response.Body)846 decoder := xml.NewDecoder(response.Body)
848 newResponse := &api.InitiateMultipartUploadResponse{}847 newResponse := &InitiateMultipartUploadResponse{}
849 848
850 err = decoder.Decode(newResponse)849 err = decoder.Decode(newResponse)
851 c.Assert(err, IsNil)850 c.Assert(err, IsNil)
vendor.json+28−0
@@ -99,6 +99,34 @@ · 98 unmodified lines
99 "local": "vendor/gopkg.in/check.v1",99 "local": "vendor/gopkg.in/check.v1",
100 "revision": "11d3bc7aa68e238947792f30573146a3231fc0f1",100 "revision": "11d3bc7aa68e238947792f30573146a3231fc0f1",
101 "revisionTime": "2015-07-29T10:04:31+02:00"101 "revisionTime": "2015-07-29T10:04:31+02:00"
102 },
103 {
104 "canonical": "gopkg.in/mgo.v2",
105 "comment": "",
106 "local": "vendor/gopkg.in/mgo.v2",
107 "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c",
108 "revisionTime": "2015-08-21T12:30:02-03:00"
109 },
110 {
111 "canonical": "gopkg.in/mgo.v2/bson",
112 "comment": "",
113 "local": "vendor/gopkg.in/mgo.v2/bson",
114 "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c",
115 "revisionTime": "2015-08-21T12:30:02-03:00"
116 },
117 {
118 "canonical": "gopkg.in/mgo.v2/internal/sasl",
119 "comment": "",
120 "local": "vendor/gopkg.in/mgo.v2/internal/sasl",
121 "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c",
122 "revisionTime": "2015-08-21T12:30:02-03:00"
123 },
124 {
125 "canonical": "gopkg.in/mgo.v2/internal/scram",
126 "comment": "",
127 "local": "vendor/gopkg.in/mgo.v2/internal/scram",
128 "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c",
129 "revisionTime": "2015-08-21T12:30:02-03:00"
102 }130 }
103 ]131 ]
104}132}
vendor/gopkg.in/mgo.v2/LICENSE+25−0
@@ -0,0 +1,25 @@
1mgo - MongoDB driver for Go
2
3Copyright (c) 2010-2013 - Gustavo Niemeyer <gustavo@niemeyer.net>
4
5All rights reserved.
6
7Redistribution and use in source and binary forms, with or without
8modification, are permitted provided that the following conditions are met:
9
101. Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
122. Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
vendor/gopkg.in/mgo.v2/Makefile+5−0
@@ -0,0 +1,5 @@
1startdb:
2 @testdb/setup.sh start
3
4stopdb:
5 @testdb/setup.sh stop
vendor/gopkg.in/mgo.v2/README.md+4−0
@@ -0,0 +1,4 @@
1The MongoDB driver for Go
2-------------------------
3
4Please go to [http://labix.org/mgo](http://labix.org/mgo) for all project details.
vendor/gopkg.in/mgo.v2/auth.go+467−0
@@ -0,0 +1,467 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29import (
30 "crypto/md5"
31 "crypto/sha1"
32 "encoding/hex"
33 "errors"
34 "fmt"
35 "sync"
36
37 "gopkg.in/mgo.v2/bson"
38 "gopkg.in/mgo.v2/internal/scram"
39)
40
41type authCmd struct {
42 Authenticate int
43
44 Nonce string
45 User string
46 Key string
47}
48
49type startSaslCmd struct {
50 StartSASL int `bson:"startSasl"`
51}
52
53type authResult struct {
54 ErrMsg string
55 Ok bool
56}
57
58type getNonceCmd struct {
59 GetNonce int
60}
61
62type getNonceResult struct {
63 Nonce string
64 Err string "$err"
65 Code int
66}
67
68type logoutCmd struct {
69 Logout int
70}
71
72type saslCmd struct {
73 Start int `bson:"saslStart,omitempty"`
74 Continue int `bson:"saslContinue,omitempty"`
75 ConversationId int `bson:"conversationId,omitempty"`
76 Mechanism string `bson:"mechanism,omitempty"`
77 Payload []byte
78}
79
80type saslResult struct {
81 Ok bool `bson:"ok"`
82 NotOk bool `bson:"code"` // Server <= 2.3.2 returns ok=1 & code>0 on errors (WTF?)
83 Done bool
84
85 ConversationId int `bson:"conversationId"`
86 Payload []byte
87 ErrMsg string
88}
89
90type saslStepper interface {
91 Step(serverData []byte) (clientData []byte, done bool, err error)
92 Close()
93}
94
95func (socket *mongoSocket) getNonce() (nonce string, err error) {
96 socket.Lock()
97 for socket.cachedNonce == "" && socket.dead == nil {
98 debugf("Socket %p to %s: waiting for nonce", socket, socket.addr)
99 socket.gotNonce.Wait()
100 }
101 if socket.cachedNonce == "mongos" {
102 socket.Unlock()
103 return "", errors.New("Can't authenticate with mongos; see http://j.mp/mongos-auth")
104 }
105 debugf("Socket %p to %s: got nonce", socket, socket.addr)
106 nonce, err = socket.cachedNonce, socket.dead
107 socket.cachedNonce = ""
108 socket.Unlock()
109 if err != nil {
110 nonce = ""
111 }
112 return
113}
114
115func (socket *mongoSocket) resetNonce() {
116 debugf("Socket %p to %s: requesting a new nonce", socket, socket.addr)
117 op := &queryOp{}
118 op.query = &getNonceCmd{GetNonce: 1}
119 op.collection = "admin.$cmd"
120 op.limit = -1
121 op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
122 if err != nil {
123 socket.kill(errors.New("getNonce: "+err.Error()), true)
124 return
125 }
126 result := &getNonceResult{}
127 err = bson.Unmarshal(docData, &result)
128 if err != nil {
129 socket.kill(errors.New("Failed to unmarshal nonce: "+err.Error()), true)
130 return
131 }
132 debugf("Socket %p to %s: nonce unmarshalled: %#v", socket, socket.addr, result)
133 if result.Code == 13390 {
134 // mongos doesn't yet support auth (see http://j.mp/mongos-auth)
135 result.Nonce = "mongos"
136 } else if result.Nonce == "" {
137 var msg string
138 if result.Err != "" {
139 msg = fmt.Sprintf("Got an empty nonce: %s (%d)", result.Err, result.Code)
140 } else {
141 msg = "Got an empty nonce"
142 }
143 socket.kill(errors.New(msg), true)
144 return
145 }
146 socket.Lock()
147 if socket.cachedNonce != "" {
148 socket.Unlock()
149 panic("resetNonce: nonce already cached")
150 }
151 socket.cachedNonce = result.Nonce
152 socket.gotNonce.Signal()
153 socket.Unlock()
154 }
155 err := socket.Query(op)
156 if err != nil {
157 socket.kill(errors.New("resetNonce: "+err.Error()), true)
158 }
159}
160
161func (socket *mongoSocket) Login(cred Credential) error {
162 socket.Lock()
163 if cred.Mechanism == "" && socket.serverInfo.MaxWireVersion >= 3 {
164 cred.Mechanism = "SCRAM-SHA-1"
165 }
166 for _, sockCred := range socket.creds {
167 if sockCred == cred {
168 debugf("Socket %p to %s: login: db=%q user=%q (already logged in)", socket, socket.addr, cred.Source, cred.Username)
169 socket.Unlock()
170 return nil
171 }
172 }
173 if socket.dropLogout(cred) {
174 debugf("Socket %p to %s: login: db=%q user=%q (cached)", socket, socket.addr, cred.Source, cred.Username)
175 socket.creds = append(socket.creds, cred)
176 socket.Unlock()
177 return nil
178 }
179 socket.Unlock()
180
181 debugf("Socket %p to %s: login: db=%q user=%q", socket, socket.addr, cred.Source, cred.Username)
182
183 var err error
184 switch cred.Mechanism {
185 case "", "MONGODB-CR", "MONGO-CR": // Name changed to MONGODB-CR in SERVER-8501.
186 err = socket.loginClassic(cred)
187 case "PLAIN":
188 err = socket.loginPlain(cred)
189 case "MONGODB-X509":
190 err = socket.loginX509(cred)
191 default:
192 // Try SASL for everything else, if it is available.
193 err = socket.loginSASL(cred)
194 }
195
196 if err != nil {
197 debugf("Socket %p to %s: login error: %s", socket, socket.addr, err)
198 } else {
199 debugf("Socket %p to %s: login successful", socket, socket.addr)
200 }
201 return err
202}
203
204func (socket *mongoSocket) loginClassic(cred Credential) error {
205 // Note that this only works properly because this function is
206 // synchronous, which means the nonce won't get reset while we're
207 // using it and any other login requests will block waiting for a
208 // new nonce provided in the defer call below.
209 nonce, err := socket.getNonce()
210 if err != nil {
211 return err
212 }
213 defer socket.resetNonce()
214
215 psum := md5.New()
216 psum.Write([]byte(cred.Username + ":mongo:" + cred.Password))
217
218 ksum := md5.New()
219 ksum.Write([]byte(nonce + cred.Username))
220 ksum.Write([]byte(hex.EncodeToString(psum.Sum(nil))))
221
222 key := hex.EncodeToString(ksum.Sum(nil))
223
224 cmd := authCmd{Authenticate: 1, User: cred.Username, Nonce: nonce, Key: key}
225 res := authResult{}
226 return socket.loginRun(cred.Source, &cmd, &res, func() error {
227 if !res.Ok {
228 return errors.New(res.ErrMsg)
229 }
230 socket.Lock()
231 socket.dropAuth(cred.Source)
232 socket.creds = append(socket.creds, cred)
233 socket.Unlock()
234 return nil
235 })
236}
237
238type authX509Cmd struct {
239 Authenticate int
240 User string
241 Mechanism string
242}
243
244func (socket *mongoSocket) loginX509(cred Credential) error {
245 cmd := authX509Cmd{Authenticate: 1, User: cred.Username, Mechanism: "MONGODB-X509"}
246 res := authResult{}
247 return socket.loginRun(cred.Source, &cmd, &res, func() error {
248 if !res.Ok {
249 return errors.New(res.ErrMsg)
250 }
251 socket.Lock()
252 socket.dropAuth(cred.Source)
253 socket.creds = append(socket.creds, cred)
254 socket.Unlock()
255 return nil
256 })
257}
258
259func (socket *mongoSocket) loginPlain(cred Credential) error {
260 cmd := saslCmd{Start: 1, Mechanism: "PLAIN", Payload: []byte("\x00" + cred.Username + "\x00" + cred.Password)}
261 res := authResult{}
262 return socket.loginRun(cred.Source, &cmd, &res, func() error {
263 if !res.Ok {
264 return errors.New(res.ErrMsg)
265 }
266 socket.Lock()
267 socket.dropAuth(cred.Source)
268 socket.creds = append(socket.creds, cred)
269 socket.Unlock()
270 return nil
271 })
272}
273
274func (socket *mongoSocket) loginSASL(cred Credential) error {
275 var sasl saslStepper
276 var err error
277 if cred.Mechanism == "SCRAM-SHA-1" {
278 // SCRAM is handled without external libraries.
279 sasl = saslNewScram(cred)
280 } else if len(cred.ServiceHost) > 0 {
281 sasl, err = saslNew(cred, cred.ServiceHost)
282 } else {
283 sasl, err = saslNew(cred, socket.Server().Addr)
284 }
285 if err != nil {
286 return err
287 }
288 defer sasl.Close()
289
290 // The goal of this logic is to carry a locked socket until the
291 // local SASL step confirms the auth is valid; the socket needs to be
292 // locked so that concurrent action doesn't leave the socket in an
293 // auth state that doesn't reflect the operations that took place.
294 // As a simple case, imagine inverting login=>logout to logout=>login.
295 //
296 // The logic below works because the lock func isn't called concurrently.
297 locked := false
298 lock := func(b bool) {
299 if locked != b {
300 locked = b
301 if b {
302 socket.Lock()
303 } else {
304 socket.Unlock()
305 }
306 }
307 }
308
309 lock(true)
310 defer lock(false)
311
312 start := 1
313 cmd := saslCmd{}
314 res := saslResult{}
315 for {
316 payload, done, err := sasl.Step(res.Payload)
317 if err != nil {
318 return err
319 }
320 if done && res.Done {
321 socket.dropAuth(cred.Source)
322 socket.creds = append(socket.creds, cred)
323 break
324 }
325 lock(false)
326
327 cmd = saslCmd{
328 Start: start,
329 Continue: 1 - start,
330 ConversationId: res.ConversationId,
331 Mechanism: cred.Mechanism,
332 Payload: payload,
333 }
334 start = 0
335 err = socket.loginRun(cred.Source, &cmd, &res, func() error {
336 // See the comment on lock for why this is necessary.
337 lock(true)
338 if !res.Ok || res.NotOk {
339 return fmt.Errorf("server returned error on SASL authentication step: %s", res.ErrMsg)
340 }
341 return nil
342 })
343 if err != nil {
344 return err
345 }
346 if done && res.Done {
347 socket.dropAuth(cred.Source)
348 socket.creds = append(socket.creds, cred)
349 break
350 }
351 }
352
353 return nil
354}
355
356func saslNewScram(cred Credential) *saslScram {
357 credsum := md5.New()
358 credsum.Write([]byte(cred.Username + ":mongo:" + cred.Password))
359 client := scram.NewClient(sha1.New, cred.Username, hex.EncodeToString(credsum.Sum(nil)))
360 return &saslScram{cred: cred, client: client}
361}
362
363type saslScram struct {
364 cred Credential
365 client *scram.Client
366}
367
368func (s *saslScram) Close() {}
369
370func (s *saslScram) Step(serverData []byte) (clientData []byte, done bool, err error) {
371 more := s.client.Step(serverData)
372 return s.client.Out(), !more, s.client.Err()
373}
374
375func (socket *mongoSocket) loginRun(db string, query, result interface{}, f func() error) error {
376 var mutex sync.Mutex
377 var replyErr error
378 mutex.Lock()
379
380 op := queryOp{}
381 op.query = query
382 op.collection = db + ".$cmd"
383 op.limit = -1
384 op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
385 defer mutex.Unlock()
386
387 if err != nil {
388 replyErr = err
389 return
390 }
391
392 err = bson.Unmarshal(docData, result)
393 if err != nil {
394 replyErr = err
395 } else {
396 // Must handle this within the read loop for the socket, so
397 // that concurrent login requests are properly ordered.
398 replyErr = f()
399 }
400 }
401
402 err := socket.Query(&op)
403 if err != nil {
404 return err
405 }
406 mutex.Lock() // Wait.
407 return replyErr
408}
409
410func (socket *mongoSocket) Logout(db string) {
411 socket.Lock()
412 cred, found := socket.dropAuth(db)
413 if found {
414 debugf("Socket %p to %s: logout: db=%q (flagged)", socket, socket.addr, db)
415 socket.logout = append(socket.logout, cred)
416 }
417 socket.Unlock()
418}
419
420func (socket *mongoSocket) LogoutAll() {
421 socket.Lock()
422 if l := len(socket.creds); l > 0 {
423 debugf("Socket %p to %s: logout all (flagged %d)", socket, socket.addr, l)
424 socket.logout = append(socket.logout, socket.creds...)
425 socket.creds = socket.creds[0:0]
426 }
427 socket.Unlock()
428}
429
430func (socket *mongoSocket) flushLogout() (ops []interface{}) {
431 socket.Lock()
432 if l := len(socket.logout); l > 0 {
433 debugf("Socket %p to %s: logout all (flushing %d)", socket, socket.addr, l)
434 for i := 0; i != l; i++ {
435 op := queryOp{}
436 op.query = &logoutCmd{1}
437 op.collection = socket.logout[i].Source + ".$cmd"
438 op.limit = -1
439 ops = append(ops, &op)
440 }
441 socket.logout = socket.logout[0:0]
442 }
443 socket.Unlock()
444 return
445}
446
447func (socket *mongoSocket) dropAuth(db string) (cred Credential, found bool) {
448 for i, sockCred := range socket.creds {
449 if sockCred.Source == db {
450 copy(socket.creds[i:], socket.creds[i+1:])
451 socket.creds = socket.creds[:len(socket.creds)-1]
452 return sockCred, true
453 }
454 }
455 return cred, false
456}
457
458func (socket *mongoSocket) dropLogout(cred Credential) (found bool) {
459 for i, sockCred := range socket.logout {
460 if sockCred == cred {
461 copy(socket.logout[i:], socket.logout[i+1:])
462 socket.logout = socket.logout[:len(socket.logout)-1]
463 return true
464 }
465 }
466 return false
467}
vendor/gopkg.in/mgo.v2/auth_test.go+1180−0
@@ -0,0 +1,1180 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo_test
28
29import (
30 "crypto/tls"
31 "flag"
32 "fmt"
33 "io/ioutil"
34 "net"
35 "net/url"
36 "os"
37 "runtime"
38 "sync"
39 "time"
40
41 . "gopkg.in/check.v1"
42 "gopkg.in/mgo.v2"
43)
44
45func (s *S) TestAuthLoginDatabase(c *C) {
46 // Test both with a normal database and with an authenticated shard.
47 for _, addr := range []string{"localhost:40002", "localhost:40203"} {
48 session, err := mgo.Dial(addr)
49 c.Assert(err, IsNil)
50 defer session.Close()
51
52 coll := session.DB("mydb").C("mycoll")
53 err = coll.Insert(M{"n": 1})
54 c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*")
55
56 admindb := session.DB("admin")
57
58 err = admindb.Login("root", "wrong")
59 c.Assert(err, ErrorMatches, "auth fail(s|ed)|.*Authentication failed.")
60
61 err = admindb.Login("root", "rapadura")
62 c.Assert(err, IsNil)
63
64 err = coll.Insert(M{"n": 1})
65 c.Assert(err, IsNil)
66 }
67}
68
69func (s *S) TestAuthLoginSession(c *C) {
70 // Test both with a normal database and with an authenticated shard.
71 for _, addr := range []string{"localhost:40002", "localhost:40203"} {
72 session, err := mgo.Dial(addr)
73 c.Assert(err, IsNil)
74 defer session.Close()
75
76 coll := session.DB("mydb").C("mycoll")
77 err = coll.Insert(M{"n": 1})
78 c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*")
79
80 cred := mgo.Credential{
81 Username: "root",
82 Password: "wrong",
83 }
84 err = session.Login(&cred)
85 c.Assert(err, ErrorMatches, "auth fail(s|ed)|.*Authentication failed.")
86
87 cred.Password = "rapadura"
88
89 err = session.Login(&cred)
90 c.Assert(err, IsNil)
91
92 err = coll.Insert(M{"n": 1})
93 c.Assert(err, IsNil)
94 }
95}
96
97func (s *S) TestAuthLoginLogout(c *C) {
98 // Test both with a normal database and with an authenticated shard.
99 for _, addr := range []string{"localhost:40002", "localhost:40203"} {
100 session, err := mgo.Dial(addr)
101 c.Assert(err, IsNil)
102 defer session.Close()
103
104 admindb := session.DB("admin")
105 err = admindb.Login("root", "rapadura")
106 c.Assert(err, IsNil)
107
108 admindb.Logout()
109
110 coll := session.DB("mydb").C("mycoll")
111 err = coll.Insert(M{"n": 1})
112 c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*")
113
114 // Must have dropped auth from the session too.
115 session = session.Copy()
116 defer session.Close()
117
118 coll = session.DB("mydb").C("mycoll")
119 err = coll.Insert(M{"n": 1})
120 c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*")
121 }
122}
123
124func (s *S) TestAuthLoginLogoutAll(c *C) {
125 session, err := mgo.Dial("localhost:40002")
126 c.Assert(err, IsNil)
127 defer session.Close()
128
129 admindb := session.DB("admin")
130 err = admindb.Login("root", "rapadura")
131 c.Assert(err, IsNil)
132
133 session.LogoutAll()
134
135 coll := session.DB("mydb").C("mycoll")
136 err = coll.Insert(M{"n": 1})
137 c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*")
138
139 // Must have dropped auth from the session too.
140 session = session.Copy()
141 defer session.Close()
142
143 coll = session.DB("mydb").C("mycoll")
144 err = coll.Insert(M{"n": 1})
145 c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*")
146}
147
148func (s *S) TestAuthUpsertUserErrors(c *C) {
149 session, err := mgo.Dial("localhost:40002")
150 c.Assert(err, IsNil)
151 defer session.Close()
152
153 admindb := session.DB("admin")
154 err = admindb.Login("root", "rapadura")
155 c.Assert(err, IsNil)
156
157 mydb := session.DB("mydb")
158
159 err = mydb.UpsertUser(&mgo.User{})
160 c.Assert(err, ErrorMatches, "user has no Username")
161
162 err = mydb.UpsertUser(&mgo.User{Username: "user", Password: "pass", UserSource: "source"})
163 c.Assert(err, ErrorMatches, "user has both Password/PasswordHash and UserSource set")
164
165 err = mydb.UpsertUser(&mgo.User{Username: "user", Password: "pass", OtherDBRoles: map[string][]mgo.Role{"db": nil}})
166 c.Assert(err, ErrorMatches, "user with OtherDBRoles is only supported in the admin or \\$external databases")
167}
168
169func (s *S) TestAuthUpsertUser(c *C) {
170 if !s.versionAtLeast(2, 4) {
171 c.Skip("UpsertUser only works on 2.4+")
172 }
173 session, err := mgo.Dial("localhost:40002")
174 c.Assert(err, IsNil)
175 defer session.Close()
176
177 admindb := session.DB("admin")
178 err = admindb.Login("root", "rapadura")
179 c.Assert(err, IsNil)
180
181 mydb := session.DB("mydb")
182
183 ruser := &mgo.User{
184 Username: "myruser",
185 Password: "mypass",
186 Roles: []mgo.Role{mgo.RoleRead},
187 }
188 rwuser := &mgo.User{
189 Username: "myrwuser",
190 Password: "mypass",
191 Roles: []mgo.Role{mgo.RoleReadWrite},
192 }
193
194 err = mydb.UpsertUser(ruser)
195 c.Assert(err, IsNil)
196 err = mydb.UpsertUser(rwuser)
197 c.Assert(err, IsNil)
198
199 err = mydb.Login("myruser", "mypass")
200 c.Assert(err, IsNil)
201
202 admindb.Logout()
203
204 coll := session.DB("mydb").C("mycoll")
205 err = coll.Insert(M{"n": 1})
206 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
207
208 err = mydb.Login("myrwuser", "mypass")
209 c.Assert(err, IsNil)
210
211 err = coll.Insert(M{"n": 1})
212 c.Assert(err, IsNil)
213
214 myotherdb := session.DB("myotherdb")
215
216 err = admindb.Login("root", "rapadura")
217 c.Assert(err, IsNil)
218
219 // Test UserSource.
220 rwuserother := &mgo.User{
221 Username: "myrwuser",
222 UserSource: "mydb",
223 Roles: []mgo.Role{mgo.RoleRead},
224 }
225
226 err = myotherdb.UpsertUser(rwuserother)
227 if s.versionAtLeast(2, 6) {
228 c.Assert(err, ErrorMatches, `MongoDB 2.6\+ does not support the UserSource setting`)
229 return
230 }
231 c.Assert(err, IsNil)
232
233 admindb.Logout()
234
235 // Test indirection via UserSource: we can't write to it, because
236 // the roles for myrwuser are different there.
237 othercoll := myotherdb.C("myothercoll")
238 err = othercoll.Insert(M{"n": 1})
239 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
240
241 // Reading works, though.
242 err = othercoll.Find(nil).One(nil)
243 c.Assert(err, Equals, mgo.ErrNotFound)
244
245 // Can't login directly into the database using UserSource, though.
246 err = myotherdb.Login("myrwuser", "mypass")
247 c.Assert(err, ErrorMatches, "auth fail(s|ed)|.*Authentication failed.")
248}
249
250func (s *S) TestAuthUpsertUserOtherDBRoles(c *C) {
251 if !s.versionAtLeast(2, 4) {
252 c.Skip("UpsertUser only works on 2.4+")
253 }
254 session, err := mgo.Dial("localhost:40002")
255 c.Assert(err, IsNil)
256 defer session.Close()
257
258 admindb := session.DB("admin")
259 err = admindb.Login("root", "rapadura")
260 c.Assert(err, IsNil)
261
262 ruser := &mgo.User{
263 Username: "myruser",
264 Password: "mypass",
265 OtherDBRoles: map[string][]mgo.Role{"mydb": []mgo.Role{mgo.RoleRead}},
266 }
267
268 err = admindb.UpsertUser(ruser)
269 c.Assert(err, IsNil)
270 defer admindb.RemoveUser("myruser")
271
272 admindb.Logout()
273 err = admindb.Login("myruser", "mypass")
274
275 coll := session.DB("mydb").C("mycoll")
276 err = coll.Insert(M{"n": 1})
277 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
278
279 err = coll.Find(nil).One(nil)
280 c.Assert(err, Equals, mgo.ErrNotFound)
281}
282
283func (s *S) TestAuthUpsertUserUpdates(c *C) {
284 if !s.versionAtLeast(2, 4) {
285 c.Skip("UpsertUser only works on 2.4+")
286 }
287 session, err := mgo.Dial("localhost:40002")
288 c.Assert(err, IsNil)
289 defer session.Close()
290
291 admindb := session.DB("admin")
292 err = admindb.Login("root", "rapadura")
293 c.Assert(err, IsNil)
294
295 mydb := session.DB("mydb")
296
297 // Insert a user that can read.
298 user := &mgo.User{
299 Username: "myruser",
300 Password: "mypass",
301 Roles: []mgo.Role{mgo.RoleRead},
302 }
303 err = mydb.UpsertUser(user)
304 c.Assert(err, IsNil)
305
306 // Now update the user password.
307 user = &mgo.User{
308 Username: "myruser",
309 Password: "mynewpass",
310 }
311 err = mydb.UpsertUser(user)
312 c.Assert(err, IsNil)
313
314 // Login with the new user.
315 usession, err := mgo.Dial("myruser:mynewpass@localhost:40002/mydb")
316 c.Assert(err, IsNil)
317 defer usession.Close()
318
319 // Can read, but not write.
320 err = usession.DB("mydb").C("mycoll").Find(nil).One(nil)
321 c.Assert(err, Equals, mgo.ErrNotFound)
322 err = usession.DB("mydb").C("mycoll").Insert(M{"ok": 1})
323 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
324
325 // Update the user role.
326 user = &mgo.User{
327 Username: "myruser",
328 Roles: []mgo.Role{mgo.RoleReadWrite},
329 }
330 err = mydb.UpsertUser(user)
331 c.Assert(err, IsNil)
332
333 // Dial again to ensure the password hasn't changed.
334 usession, err = mgo.Dial("myruser:mynewpass@localhost:40002/mydb")
335 c.Assert(err, IsNil)
336 defer usession.Close()
337
338 // Now it can write.
339 err = usession.DB("mydb").C("mycoll").Insert(M{"ok": 1})
340 c.Assert(err, IsNil)
341}
342
343func (s *S) TestAuthAddUser(c *C) {
344 session, err := mgo.Dial("localhost:40002")
345 c.Assert(err, IsNil)
346 defer session.Close()
347
348 admindb := session.DB("admin")
349 err = admindb.Login("root", "rapadura")
350 c.Assert(err, IsNil)
351
352 mydb := session.DB("mydb")
353 err = mydb.AddUser("myruser", "mypass", true)
354 c.Assert(err, IsNil)
355 err = mydb.AddUser("mywuser", "mypass", false)
356 c.Assert(err, IsNil)
357
358 err = mydb.Login("myruser", "mypass")
359 c.Assert(err, IsNil)
360
361 admindb.Logout()
362
363 coll := session.DB("mydb").C("mycoll")
364 err = coll.Insert(M{"n": 1})
365 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
366
367 err = mydb.Login("mywuser", "mypass")
368 c.Assert(err, IsNil)
369
370 err = coll.Insert(M{"n": 1})
371 c.Assert(err, IsNil)
372}
373
374func (s *S) TestAuthAddUserReplaces(c *C) {
375 session, err := mgo.Dial("localhost:40002")
376 c.Assert(err, IsNil)
377 defer session.Close()
378
379 admindb := session.DB("admin")
380 err = admindb.Login("root", "rapadura")
381 c.Assert(err, IsNil)
382
383 mydb := session.DB("mydb")
384 err = mydb.AddUser("myuser", "myoldpass", false)
385 c.Assert(err, IsNil)
386 err = mydb.AddUser("myuser", "mynewpass", true)
387 c.Assert(err, IsNil)
388
389 admindb.Logout()
390
391 err = mydb.Login("myuser", "myoldpass")
392 c.Assert(err, ErrorMatches, "auth fail(s|ed)|.*Authentication failed.")
393 err = mydb.Login("myuser", "mynewpass")
394 c.Assert(err, IsNil)
395
396 // ReadOnly flag was changed too.
397 err = mydb.C("mycoll").Insert(M{"n": 1})
398 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
399}
400
401func (s *S) TestAuthRemoveUser(c *C) {
402 session, err := mgo.Dial("localhost:40002")
403 c.Assert(err, IsNil)
404 defer session.Close()
405
406 admindb := session.DB("admin")
407 err = admindb.Login("root", "rapadura")
408 c.Assert(err, IsNil)
409
410 mydb := session.DB("mydb")
411 err = mydb.AddUser("myuser", "mypass", true)
412 c.Assert(err, IsNil)
413 err = mydb.RemoveUser("myuser")
414 c.Assert(err, IsNil)
415 err = mydb.RemoveUser("myuser")
416 c.Assert(err, Equals, mgo.ErrNotFound)
417
418 err = mydb.Login("myuser", "mypass")
419 c.Assert(err, ErrorMatches, "auth fail(s|ed)|.*Authentication failed.")
420}
421
422func (s *S) TestAuthLoginTwiceDoesNothing(c *C) {
423 session, err := mgo.Dial("localhost:40002")
424 c.Assert(err, IsNil)
425 defer session.Close()
426
427 admindb := session.DB("admin")
428 err = admindb.Login("root", "rapadura")
429 c.Assert(err, IsNil)
430
431 oldStats := mgo.GetStats()
432
433 err = admindb.Login("root", "rapadura")
434 c.Assert(err, IsNil)
435
436 newStats := mgo.GetStats()
437 c.Assert(newStats.SentOps, Equals, oldStats.SentOps)
438}
439
440func (s *S) TestAuthLoginLogoutLoginDoesNothing(c *C) {
441 session, err := mgo.Dial("localhost:40002")
442 c.Assert(err, IsNil)
443 defer session.Close()
444
445 admindb := session.DB("admin")
446 err = admindb.Login("root", "rapadura")
447 c.Assert(err, IsNil)
448
449 oldStats := mgo.GetStats()
450
451 admindb.Logout()
452 err = admindb.Login("root", "rapadura")
453 c.Assert(err, IsNil)
454
455 newStats := mgo.GetStats()
456 c.Assert(newStats.SentOps, Equals, oldStats.SentOps)
457}
458
459func (s *S) TestAuthLoginSwitchUser(c *C) {
460 session, err := mgo.Dial("localhost:40002")
461 c.Assert(err, IsNil)
462 defer session.Close()
463
464 admindb := session.DB("admin")
465 err = admindb.Login("root", "rapadura")
466 c.Assert(err, IsNil)
467
468 coll := session.DB("mydb").C("mycoll")
469 err = coll.Insert(M{"n": 1})
470 c.Assert(err, IsNil)
471
472 err = admindb.Login("reader", "rapadura")
473 c.Assert(err, IsNil)
474
475 // Can't write.
476 err = coll.Insert(M{"n": 1})
477 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
478
479 // But can read.
480 result := struct{ N int }{}
481 err = coll.Find(nil).One(&result)
482 c.Assert(err, IsNil)
483 c.Assert(result.N, Equals, 1)
484}
485
486func (s *S) TestAuthLoginChangePassword(c *C) {
487 session, err := mgo.Dial("localhost:40002")
488 c.Assert(err, IsNil)
489 defer session.Close()
490
491 admindb := session.DB("admin")
492 err = admindb.Login("root", "rapadura")
493 c.Assert(err, IsNil)
494
495 mydb := session.DB("mydb")
496 err = mydb.AddUser("myuser", "myoldpass", false)
497 c.Assert(err, IsNil)
498
499 err = mydb.Login("myuser", "myoldpass")
500 c.Assert(err, IsNil)
501
502 err = mydb.AddUser("myuser", "mynewpass", true)
503 c.Assert(err, IsNil)
504
505 err = mydb.Login("myuser", "mynewpass")
506 c.Assert(err, IsNil)
507
508 admindb.Logout()
509
510 // The second login must be in effect, which means read-only.
511 err = mydb.C("mycoll").Insert(M{"n": 1})
512 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
513}
514
515func (s *S) TestAuthLoginCachingWithSessionRefresh(c *C) {
516 session, err := mgo.Dial("localhost:40002")
517 c.Assert(err, IsNil)
518 defer session.Close()
519
520 admindb := session.DB("admin")
521 err = admindb.Login("root", "rapadura")
522 c.Assert(err, IsNil)
523
524 session.Refresh()
525
526 coll := session.DB("mydb").C("mycoll")
527 err = coll.Insert(M{"n": 1})
528 c.Assert(err, IsNil)
529}
530
531func (s *S) TestAuthLoginCachingWithSessionCopy(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 session = session.Copy()
541 defer session.Close()
542
543 coll := session.DB("mydb").C("mycoll")
544 err = coll.Insert(M{"n": 1})
545 c.Assert(err, IsNil)
546}
547
548func (s *S) TestAuthLoginCachingWithSessionClone(c *C) {
549 session, err := mgo.Dial("localhost:40002")
550 c.Assert(err, IsNil)
551 defer session.Close()
552
553 admindb := session.DB("admin")
554 err = admindb.Login("root", "rapadura")
555 c.Assert(err, IsNil)
556
557 session = session.Clone()
558 defer session.Close()
559
560 coll := session.DB("mydb").C("mycoll")
561 err = coll.Insert(M{"n": 1})
562 c.Assert(err, IsNil)
563}
564
565func (s *S) TestAuthLoginCachingWithNewSession(c *C) {
566 session, err := mgo.Dial("localhost:40002")
567 c.Assert(err, IsNil)
568 defer session.Close()
569
570 admindb := session.DB("admin")
571 err = admindb.Login("root", "rapadura")
572 c.Assert(err, IsNil)
573
574 session = session.New()
575 defer session.Close()
576
577 coll := session.DB("mydb").C("mycoll")
578 err = coll.Insert(M{"n": 1})
579 c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*")
580}
581
582func (s *S) TestAuthLoginCachingAcrossPool(c *C) {
583 // Logins are cached even when the conenction goes back
584 // into the pool.
585
586 session, err := mgo.Dial("localhost:40002")
587 c.Assert(err, IsNil)
588 defer session.Close()
589
590 admindb := session.DB("admin")
591 err = admindb.Login("root", "rapadura")
592 c.Assert(err, IsNil)
593
594 // Add another user to test the logout case at the same time.
595 mydb := session.DB("mydb")
596 err = mydb.AddUser("myuser", "mypass", false)
597 c.Assert(err, IsNil)
598
599 err = mydb.Login("myuser", "mypass")
600 c.Assert(err, IsNil)
601
602 // Logout root explicitly, to test both cases.
603 admindb.Logout()
604
605 // Give socket back to pool.
606 session.Refresh()
607
608 // Brand new session, should use socket from the pool.
609 other := session.New()
610 defer other.Close()
611
612 oldStats := mgo.GetStats()
613
614 err = other.DB("admin").Login("root", "rapadura")
615 c.Assert(err, IsNil)
616 err = other.DB("mydb").Login("myuser", "mypass")
617 c.Assert(err, IsNil)
618
619 // Both logins were cached, so no ops.
620 newStats := mgo.GetStats()
621 c.Assert(newStats.SentOps, Equals, oldStats.SentOps)
622
623 // And they actually worked.
624 err = other.DB("mydb").C("mycoll").Insert(M{"n": 1})
625 c.Assert(err, IsNil)
626
627 other.DB("admin").Logout()
628
629 err = other.DB("mydb").C("mycoll").Insert(M{"n": 1})
630 c.Assert(err, IsNil)
631}
632
633func (s *S) TestAuthLoginCachingAcrossPoolWithLogout(c *C) {
634 // Now verify that logouts are properly flushed if they
635 // are not revalidated after leaving the pool.
636
637 session, err := mgo.Dial("localhost:40002")
638 c.Assert(err, IsNil)
639 defer session.Close()
640
641 admindb := session.DB("admin")
642 err = admindb.Login("root", "rapadura")
643 c.Assert(err, IsNil)
644
645 // Add another user to test the logout case at the same time.
646 mydb := session.DB("mydb")
647 err = mydb.AddUser("myuser", "mypass", true)
648 c.Assert(err, IsNil)
649
650 err = mydb.Login("myuser", "mypass")
651 c.Assert(err, IsNil)
652
653 // Just some data to query later.
654 err = session.DB("mydb").C("mycoll").Insert(M{"n": 1})
655 c.Assert(err, IsNil)
656
657 // Give socket back to pool.
658 session.Refresh()
659
660 // Brand new session, should use socket from the pool.
661 other := session.New()
662 defer other.Close()
663
664 oldStats := mgo.GetStats()
665
666 err = other.DB("mydb").Login("myuser", "mypass")
667 c.Assert(err, IsNil)
668
669 // Login was cached, so no ops.
670 newStats := mgo.GetStats()
671 c.Assert(newStats.SentOps, Equals, oldStats.SentOps)
672
673 // Can't write, since root has been implicitly logged out
674 // when the collection went into the pool, and not revalidated.
675 err = other.DB("mydb").C("mycoll").Insert(M{"n": 1})
676 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
677
678 // But can read due to the revalidated myuser login.
679 result := struct{ N int }{}
680 err = other.DB("mydb").C("mycoll").Find(nil).One(&result)
681 c.Assert(err, IsNil)
682 c.Assert(result.N, Equals, 1)
683}
684
685func (s *S) TestAuthEventual(c *C) {
686 // Eventual sessions don't keep sockets around, so they are
687 // an interesting test case.
688 session, err := mgo.Dial("localhost:40002")
689 c.Assert(err, IsNil)
690 defer session.Close()
691
692 admindb := session.DB("admin")
693 err = admindb.Login("root", "rapadura")
694 c.Assert(err, IsNil)
695
696 err = session.DB("mydb").C("mycoll").Insert(M{"n": 1})
697 c.Assert(err, IsNil)
698
699 var wg sync.WaitGroup
700 wg.Add(20)
701
702 for i := 0; i != 10; i++ {
703 go func() {
704 defer wg.Done()
705 var result struct{ N int }
706 err := session.DB("mydb").C("mycoll").Find(nil).One(&result)
707 c.Assert(err, IsNil)
708 c.Assert(result.N, Equals, 1)
709 }()
710 }
711
712 for i := 0; i != 10; i++ {
713 go func() {
714 defer wg.Done()
715 err := session.DB("mydb").C("mycoll").Insert(M{"n": 1})
716 c.Assert(err, IsNil)
717 }()
718 }
719
720 wg.Wait()
721}
722
723func (s *S) TestAuthURL(c *C) {
724 session, err := mgo.Dial("mongodb://root:rapadura@localhost:40002/")
725 c.Assert(err, IsNil)
726 defer session.Close()
727
728 err = session.DB("mydb").C("mycoll").Insert(M{"n": 1})
729 c.Assert(err, IsNil)
730}
731
732func (s *S) TestAuthURLWrongCredentials(c *C) {
733 session, err := mgo.Dial("mongodb://root:wrong@localhost:40002/")
734 if session != nil {
735 session.Close()
736 }
737 c.Assert(err, ErrorMatches, "auth fail(s|ed)|.*Authentication failed.")
738 c.Assert(session, IsNil)
739}
740
741func (s *S) TestAuthURLWithNewSession(c *C) {
742 // When authentication is in the URL, the new session will
743 // actually carry it on as well, even if logged out explicitly.
744 session, err := mgo.Dial("mongodb://root:rapadura@localhost:40002/")
745 c.Assert(err, IsNil)
746 defer session.Close()
747
748 session.DB("admin").Logout()
749
750 // Do it twice to ensure it passes the needed data on.
751 session = session.New()
752 defer session.Close()
753 session = session.New()
754 defer session.Close()
755
756 err = session.DB("mydb").C("mycoll").Insert(M{"n": 1})
757 c.Assert(err, IsNil)
758}
759
760func (s *S) TestAuthURLWithDatabase(c *C) {
761 session, err := mgo.Dial("mongodb://root:rapadura@localhost:40002")
762 c.Assert(err, IsNil)
763 defer session.Close()
764
765 mydb := session.DB("mydb")
766 err = mydb.AddUser("myruser", "mypass", true)
767 c.Assert(err, IsNil)
768
769 // Test once with database, and once with source.
770 for i := 0; i < 2; i++ {
771 var url string
772 if i == 0 {
773 url = "mongodb://myruser:mypass@localhost:40002/mydb"
774 } else {
775 url = "mongodb://myruser:mypass@localhost:40002/admin?authSource=mydb"
776 }
777 usession, err := mgo.Dial(url)
778 c.Assert(err, IsNil)
779 defer usession.Close()
780
781 ucoll := usession.DB("mydb").C("mycoll")
782 err = ucoll.FindId(0).One(nil)
783 c.Assert(err, Equals, mgo.ErrNotFound)
784 err = ucoll.Insert(M{"n": 1})
785 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
786 }
787}
788
789func (s *S) TestDefaultDatabase(c *C) {
790 tests := []struct{ url, db string }{
791 {"mongodb://root:rapadura@localhost:40002", "test"},
792 {"mongodb://root:rapadura@localhost:40002/admin", "admin"},
793 {"mongodb://localhost:40001", "test"},
794 {"mongodb://localhost:40001/", "test"},
795 {"mongodb://localhost:40001/mydb", "mydb"},
796 }
797
798 for _, test := range tests {
799 session, err := mgo.Dial(test.url)
800 c.Assert(err, IsNil)
801 defer session.Close()
802
803 c.Logf("test: %#v", test)
804 c.Assert(session.DB("").Name, Equals, test.db)
805
806 scopy := session.Copy()
807 c.Check(scopy.DB("").Name, Equals, test.db)
808 scopy.Close()
809 }
810}
811
812func (s *S) TestAuthDirect(c *C) {
813 // Direct connections must work to the master and slaves.
814 for _, port := range []string{"40031", "40032", "40033"} {
815 url := fmt.Sprintf("mongodb://root:rapadura@localhost:%s/?connect=direct", port)
816 session, err := mgo.Dial(url)
817 c.Assert(err, IsNil)
818 defer session.Close()
819
820 session.SetMode(mgo.Monotonic, true)
821
822 var result struct{}
823 err = session.DB("mydb").C("mycoll").Find(nil).One(&result)
824 c.Assert(err, Equals, mgo.ErrNotFound)
825 }
826}
827
828func (s *S) TestAuthDirectWithLogin(c *C) {
829 // Direct connections must work to the master and slaves.
830 for _, port := range []string{"40031", "40032", "40033"} {
831 url := fmt.Sprintf("mongodb://localhost:%s/?connect=direct", port)
832 session, err := mgo.Dial(url)
833 c.Assert(err, IsNil)
834 defer session.Close()
835
836 session.SetMode(mgo.Monotonic, true)
837 session.SetSyncTimeout(3 * time.Second)
838
839 err = session.DB("admin").Login("root", "rapadura")
840 c.Assert(err, IsNil)
841
842 var result struct{}
843 err = session.DB("mydb").C("mycoll").Find(nil).One(&result)
844 c.Assert(err, Equals, mgo.ErrNotFound)
845 }
846}
847
848func (s *S) TestAuthScramSha1Cred(c *C) {
849 if !s.versionAtLeast(2, 7, 7) {
850 c.Skip("SCRAM-SHA-1 tests depend on 2.7.7")
851 }
852 cred := &mgo.Credential{
853 Username: "root",
854 Password: "rapadura",
855 Mechanism: "SCRAM-SHA-1",
856 Source: "admin",
857 }
858 host := "localhost:40002"
859 c.Logf("Connecting to %s...", host)
860 session, err := mgo.Dial(host)
861 c.Assert(err, IsNil)
862 defer session.Close()
863
864 mycoll := session.DB("admin").C("mycoll")
865
866 c.Logf("Connected! Testing the need for authentication...")
867 err = mycoll.Find(nil).One(nil)
868 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
869
870 c.Logf("Authenticating...")
871 err = session.Login(cred)
872 c.Assert(err, IsNil)
873 c.Logf("Authenticated!")
874
875 c.Logf("Connected! Testing the need for authentication...")
876 err = mycoll.Find(nil).One(nil)
877 c.Assert(err, Equals, mgo.ErrNotFound)
878}
879
880func (s *S) TestAuthScramSha1URL(c *C) {
881 if !s.versionAtLeast(2, 7, 7) {
882 c.Skip("SCRAM-SHA-1 tests depend on 2.7.7")
883 }
884 host := "localhost:40002"
885 c.Logf("Connecting to %s...", host)
886 session, err := mgo.Dial(fmt.Sprintf("root:rapadura@%s?authMechanism=SCRAM-SHA-1", host))
887 c.Assert(err, IsNil)
888 defer session.Close()
889
890 mycoll := session.DB("admin").C("mycoll")
891
892 c.Logf("Connected! Testing the need for authentication...")
893 err = mycoll.Find(nil).One(nil)
894 c.Assert(err, Equals, mgo.ErrNotFound)
895}
896
897func (s *S) TestAuthX509Cred(c *C) {
898 session, err := mgo.Dial("localhost:40001")
899 c.Assert(err, IsNil)
900 defer session.Close()
901 binfo, err := session.BuildInfo()
902 c.Assert(err, IsNil)
903 if binfo.OpenSSLVersion == "" {
904 c.Skip("server does not support SSL")
905 }
906
907 clientCertPEM, err := ioutil.ReadFile("testdb/client.pem")
908 c.Assert(err, IsNil)
909
910 clientCert, err := tls.X509KeyPair(clientCertPEM, clientCertPEM)
911 c.Assert(err, IsNil)
912
913 tlsConfig := &tls.Config{
914 // Isolating tests to client certs, don't care about server validation.
915 InsecureSkipVerify: true,
916 Certificates: []tls.Certificate{clientCert},
917 }
918
919 var host = "localhost:40003"
920 c.Logf("Connecting to %s...", host)
921 session, err = mgo.DialWithInfo(&mgo.DialInfo{
922 Addrs: []string{host},
923 DialServer: func(addr *mgo.ServerAddr) (net.Conn, error) {
924 return tls.Dial("tcp", addr.String(), tlsConfig)
925 },
926 })
927 c.Assert(err, IsNil)
928 defer session.Close()
929
930 err = session.Login(&mgo.Credential{Username: "root", Password: "rapadura"})
931 c.Assert(err, IsNil)
932
933 // This needs to be kept in sync with client.pem
934 x509Subject := "CN=localhost,OU=Client,O=MGO,L=MGO,ST=MGO,C=GO"
935
936 externalDB := session.DB("$external")
937 var x509User mgo.User = mgo.User{
938 Username: x509Subject,
939 OtherDBRoles: map[string][]mgo.Role{"admin": []mgo.Role{mgo.RoleRoot}},
940 }
941 err = externalDB.UpsertUser(&x509User)
942 c.Assert(err, IsNil)
943
944 session.LogoutAll()
945
946 c.Logf("Connected! Ensuring authentication is required...")
947 names, err := session.DatabaseNames()
948 c.Assert(err, ErrorMatches, "not authorized .*")
949
950 cred := &mgo.Credential{
951 Username: x509Subject,
952 Mechanism: "MONGODB-X509",
953 Source: "$external",
954 }
955
956 c.Logf("Authenticating...")
957 err = session.Login(cred)
958 c.Assert(err, IsNil)
959 c.Logf("Authenticated!")
960
961 names, err = session.DatabaseNames()
962 c.Assert(err, IsNil)
963 c.Assert(len(names) > 0, Equals, true)
964}
965
966var (
967 plainFlag = flag.String("plain", "", "Host to test PLAIN authentication against (depends on custom environment)")
968 plainUser = "einstein"
969 plainPass = "password"
970)
971
972func (s *S) TestAuthPlainCred(c *C) {
973 if *plainFlag == "" {
974 c.Skip("no -plain")
975 }
976 cred := &mgo.Credential{
977 Username: plainUser,
978 Password: plainPass,
979 Source: "$external",
980 Mechanism: "PLAIN",
981 }
982 c.Logf("Connecting to %s...", *plainFlag)
983 session, err := mgo.Dial(*plainFlag)
984 c.Assert(err, IsNil)
985 defer session.Close()
986
987 records := session.DB("records").C("records")
988
989 c.Logf("Connected! Testing the need for authentication...")
990 err = records.Find(nil).One(nil)
991 c.Assert(err, ErrorMatches, "unauthorized|not authorized .*")
992
993 c.Logf("Authenticating...")
994 err = session.Login(cred)
995 c.Assert(err, IsNil)
996 c.Logf("Authenticated!")
997
998 c.Logf("Connected! Testing the need for authentication...")
999 err = records.Find(nil).One(nil)
1000 c.Assert(err, Equals, mgo.ErrNotFound)
1001}
1002
1003func (s *S) TestAuthPlainURL(c *C) {
1004 if *plainFlag == "" {
1005 c.Skip("no -plain")
1006 }
1007 c.Logf("Connecting to %s...", *plainFlag)
1008 session, err := mgo.Dial(fmt.Sprintf("%s:%s@%s?authMechanism=PLAIN", url.QueryEscape(plainUser), url.QueryEscape(plainPass), *plainFlag))
1009 c.Assert(err, IsNil)
1010 defer session.Close()
1011
1012 c.Logf("Connected! Testing the need for authentication...")
1013 err = session.DB("records").C("records").Find(nil).One(nil)
1014 c.Assert(err, Equals, mgo.ErrNotFound)
1015}
1016
1017var (
1018 kerberosFlag = flag.Bool("kerberos", false, "Test Kerberos authentication (depends on custom environment)")
1019 kerberosHost = "ldaptest.10gen.cc"
1020 kerberosUser = "drivers@LDAPTEST.10GEN.CC"
1021
1022 winKerberosPasswordEnv = "MGO_KERBEROS_PASSWORD"
1023)
1024
1025// Kerberos has its own suite because it talks to a remote server
1026// that is prepared to authenticate against a kerberos deployment.
1027type KerberosSuite struct{}
1028
1029var _ = Suite(&KerberosSuite{})
1030
1031func (kerberosSuite *KerberosSuite) SetUpSuite(c *C) {
1032 mgo.SetDebug(true)
1033 mgo.SetStats(true)
1034}
1035
1036func (kerberosSuite *KerberosSuite) TearDownSuite(c *C) {
1037 mgo.SetDebug(false)
1038 mgo.SetStats(false)
1039}
1040
1041func (kerberosSuite *KerberosSuite) SetUpTest(c *C) {
1042 mgo.SetLogger((*cLogger)(c))
1043 mgo.ResetStats()
1044}
1045
1046func (kerberosSuite *KerberosSuite) TearDownTest(c *C) {
1047 mgo.SetLogger(nil)
1048}
1049
1050func (kerberosSuite *KerberosSuite) TestAuthKerberosCred(c *C) {
1051 if !*kerberosFlag {
1052 c.Skip("no -kerberos")
1053 }
1054 cred := &mgo.Credential{
1055 Username: kerberosUser,
1056 Mechanism: "GSSAPI",
1057 }
1058 windowsAppendPasswordToCredential(cred)
1059 c.Logf("Connecting to %s...", kerberosHost)
1060 session, err := mgo.Dial(kerberosHost)
1061 c.Assert(err, IsNil)
1062 defer session.Close()
1063
1064 c.Logf("Connected! Testing the need for authentication...")
1065 n, err := session.DB("kerberos").C("test").Find(M{}).Count()
1066 c.Assert(err, ErrorMatches, ".*authorized.*")
1067
1068 c.Logf("Authenticating...")
1069 err = session.Login(cred)
1070 c.Assert(err, IsNil)
1071 c.Logf("Authenticated!")
1072
1073 n, err = session.DB("kerberos").C("test").Find(M{}).Count()
1074 c.Assert(err, IsNil)
1075 c.Assert(n, Equals, 1)
1076}
1077
1078func (kerberosSuite *KerberosSuite) TestAuthKerberosURL(c *C) {
1079 if !*kerberosFlag {
1080 c.Skip("no -kerberos")
1081 }
1082 c.Logf("Connecting to %s...", kerberosHost)
1083 connectUri := url.QueryEscape(kerberosUser) + "@" + kerberosHost + "?authMechanism=GSSAPI"
1084 if runtime.GOOS == "windows" {
1085 connectUri = url.QueryEscape(kerberosUser) + ":" + url.QueryEscape(getWindowsKerberosPassword()) + "@" + kerberosHost + "?authMechanism=GSSAPI"
1086 }
1087 session, err := mgo.Dial(connectUri)
1088 c.Assert(err, IsNil)
1089 defer session.Close()
1090 n, err := session.DB("kerberos").C("test").Find(M{}).Count()
1091 c.Assert(err, IsNil)
1092 c.Assert(n, Equals, 1)
1093}
1094
1095func (kerberosSuite *KerberosSuite) TestAuthKerberosServiceName(c *C) {
1096 if !*kerberosFlag {
1097 c.Skip("no -kerberos")
1098 }
1099
1100 wrongServiceName := "wrong"
1101 rightServiceName := "mongodb"
1102
1103 cred := &mgo.Credential{
1104 Username: kerberosUser,
1105 Mechanism: "GSSAPI",
1106 Service: wrongServiceName,
1107 }
1108 windowsAppendPasswordToCredential(cred)
1109
1110 c.Logf("Connecting to %s...", kerberosHost)
1111 session, err := mgo.Dial(kerberosHost)
1112 c.Assert(err, IsNil)
1113 defer session.Close()
1114
1115 c.Logf("Authenticating with incorrect service name...")
1116 err = session.Login(cred)
1117 c.Assert(err, ErrorMatches, ".*@LDAPTEST.10GEN.CC not found.*")
1118
1119 cred.Service = rightServiceName
1120 c.Logf("Authenticating with correct service name...")
1121 err = session.Login(cred)
1122 c.Assert(err, IsNil)
1123 c.Logf("Authenticated!")
1124
1125 n, err := session.DB("kerberos").C("test").Find(M{}).Count()
1126 c.Assert(err, IsNil)
1127 c.Assert(n, Equals, 1)
1128}
1129
1130func (kerberosSuite *KerberosSuite) TestAuthKerberosServiceHost(c *C) {
1131 if !*kerberosFlag {
1132 c.Skip("no -kerberos")
1133 }
1134
1135 wrongServiceHost := "eggs.bacon.tk"
1136 rightServiceHost := kerberosHost
1137
1138 cred := &mgo.Credential{
1139 Username: kerberosUser,
1140 Mechanism: "GSSAPI",
1141 ServiceHost: wrongServiceHost,
1142 }
1143 windowsAppendPasswordToCredential(cred)
1144
1145 c.Logf("Connecting to %s...", kerberosHost)
1146 session, err := mgo.Dial(kerberosHost)
1147 c.Assert(err, IsNil)
1148 defer session.Close()
1149
1150 c.Logf("Authenticating with incorrect service host...")
1151 err = session.Login(cred)
1152 c.Assert(err, ErrorMatches, ".*@LDAPTEST.10GEN.CC not found.*")
1153
1154 cred.ServiceHost = rightServiceHost
1155 c.Logf("Authenticating with correct service host...")
1156 err = session.Login(cred)
1157 c.Assert(err, IsNil)
1158 c.Logf("Authenticated!")
1159
1160 n, err := session.DB("kerberos").C("test").Find(M{}).Count()
1161 c.Assert(err, IsNil)
1162 c.Assert(n, Equals, 1)
1163}
1164
1165// No kinit on SSPI-style Kerberos, so we need to provide a password. In order
1166// to avoid inlining password, require it to be set as an environment variable,
1167// for instance: `SET MGO_KERBEROS_PASSWORD=this_isnt_the_password`
1168func getWindowsKerberosPassword() string {
1169 pw := os.Getenv(winKerberosPasswordEnv)
1170 if pw == "" {
1171 panic(fmt.Sprintf("Need to set %v environment variable to run Kerberos tests on Windows", winKerberosPasswordEnv))
1172 }
1173 return pw
1174}
1175
1176func windowsAppendPasswordToCredential(cred *mgo.Credential) {
1177 if runtime.GOOS == "windows" {
1178 cred.Password = getWindowsKerberosPassword()
1179 }
1180}
vendor/gopkg.in/mgo.v2/bson/LICENSE+25−0
@@ -0,0 +1,25 @@
1BSON library for Go
2
3Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4
5All rights reserved.
6
7Redistribution and use in source and binary forms, with or without
8modification, are permitted provided that the following conditions are met:
9
101. Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
122. Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
vendor/gopkg.in/mgo.v2/bson/bson.go+705−0
@@ -0,0 +1,705 @@
1// BSON library for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27// Package bson is an implementation of the BSON specification for Go:
28//
29// http://bsonspec.org
30//
31// It was created as part of the mgo MongoDB driver for Go, but is standalone
32// and may be used on its own without the driver.
33package bson
34
35import (
36 "bytes"
37 "crypto/md5"
38 "crypto/rand"
39 "encoding/binary"
40 "encoding/hex"
41 "errors"
42 "fmt"
43 "io"
44 "os"
45 "reflect"
46 "runtime"
47 "strings"
48 "sync"
49 "sync/atomic"
50 "time"
51)
52
53// --------------------------------------------------------------------------
54// The public API.
55
56// A value implementing the bson.Getter interface will have its GetBSON
57// method called when the given value has to be marshalled, and the result
58// of this method will be marshaled in place of the actual object.
59//
60// If GetBSON returns return a non-nil error, the marshalling procedure
61// will stop and error out with the provided value.
62type Getter interface {
63 GetBSON() (interface{}, error)
64}
65
66// A value implementing the bson.Setter interface will receive the BSON
67// value via the SetBSON method during unmarshaling, and the object
68// itself will not be changed as usual.
69//
70// If setting the value works, the method should return nil or alternatively
71// bson.SetZero to set the respective field to its zero value (nil for
72// pointer types). If SetBSON returns a value of type bson.TypeError, the
73// BSON value will be omitted from a map or slice being decoded and the
74// unmarshalling will continue. If it returns any other non-nil error, the
75// unmarshalling procedure will stop and error out with the provided value.
76//
77// This interface is generally useful in pointer receivers, since the method
78// will want to change the receiver. A type field that implements the Setter
79// interface doesn't have to be a pointer, though.
80//
81// Unlike the usual behavior, unmarshalling onto a value that implements a
82// Setter interface will NOT reset the value to its zero state. This allows
83// the value to decide by itself how to be unmarshalled.
84//
85// For example:
86//
87// type MyString string
88//
89// func (s *MyString) SetBSON(raw bson.Raw) error {
90// return raw.Unmarshal(s)
91// }
92//
93type Setter interface {
94 SetBSON(raw Raw) error
95}
96
97// SetZero may be returned from a SetBSON method to have the value set to
98// its respective zero value. When used in pointer values, this will set the
99// field to nil rather than to the pre-allocated value.
100var SetZero = errors.New("set to zero")
101
102// M is a convenient alias for a map[string]interface{} map, useful for
103// dealing with BSON in a native way. For instance:
104//
105// bson.M{"a": 1, "b": true}
106//
107// There's no special handling for this type in addition to what's done anyway
108// for an equivalent map type. Elements in the map will be dumped in an
109// undefined ordered. See also the bson.D type for an ordered alternative.
110type M map[string]interface{}
111
112// D represents a BSON document containing ordered elements. For example:
113//
114// bson.D{{"a", 1}, {"b", true}}
115//
116// In some situations, such as when creating indexes for MongoDB, the order in
117// which the elements are defined is important. If the order is not important,
118// using a map is generally more comfortable. See bson.M and bson.RawD.
119type D []DocElem
120
121// DocElem is an element of the bson.D document representation.
122type DocElem struct {
123 Name string
124 Value interface{}
125}
126
127// Map returns a map out of the ordered element name/value pairs in d.
128func (d D) Map() (m M) {
129 m = make(M, len(d))
130 for _, item := range d {
131 m[item.Name] = item.Value
132 }
133 return m
134}
135
136// The Raw type represents raw unprocessed BSON documents and elements.
137// Kind is the kind of element as defined per the BSON specification, and
138// Data is the raw unprocessed data for the respective element.
139// Using this type it is possible to unmarshal or marshal values partially.
140//
141// Relevant documentation:
142//
143// http://bsonspec.org/#/specification
144//
145type Raw struct {
146 Kind byte
147 Data []byte
148}
149
150// RawD represents a BSON document containing raw unprocessed elements.
151// This low-level representation may be useful when lazily processing
152// documents of uncertain content, or when manipulating the raw content
153// documents in general.
154type RawD []RawDocElem
155
156// See the RawD type.
157type RawDocElem struct {
158 Name string
159 Value Raw
160}
161
162// ObjectId is a unique ID identifying a BSON value. It must be exactly 12 bytes
163// long. MongoDB objects by default have such a property set in their "_id"
164// property.
165//
166// http://www.mongodb.org/display/DOCS/Object+IDs
167type ObjectId string
168
169// ObjectIdHex returns an ObjectId from the provided hex representation.
170// Calling this function with an invalid hex representation will
171// cause a runtime panic. See the IsObjectIdHex function.
172func ObjectIdHex(s string) ObjectId {
173 d, err := hex.DecodeString(s)
174 if err != nil || len(d) != 12 {
175 panic(fmt.Sprintf("Invalid input to ObjectIdHex: %q", s))
176 }
177 return ObjectId(d)
178}
179
180// IsObjectIdHex returns whether s is a valid hex representation of
181// an ObjectId. See the ObjectIdHex function.
182func IsObjectIdHex(s string) bool {
183 if len(s) != 24 {
184 return false
185 }
186 _, err := hex.DecodeString(s)
187 return err == nil
188}
189
190// objectIdCounter is atomically incremented when generating a new ObjectId
191// using NewObjectId() function. It's used as a counter part of an id.
192var objectIdCounter uint32 = 0
193
194// machineId stores machine id generated once and used in subsequent calls
195// to NewObjectId function.
196var machineId = readMachineId()
197
198// readMachineId generates machine id and puts it into the machineId global
199// variable. If this function fails to get the hostname, it will cause
200// a runtime error.
201func readMachineId() []byte {
202 var sum [3]byte
203 id := sum[:]
204 hostname, err1 := os.Hostname()
205 if err1 != nil {
206 _, err2 := io.ReadFull(rand.Reader, id)
207 if err2 != nil {
208 panic(fmt.Errorf("cannot get hostname: %v; %v", err1, err2))
209 }
210 return id
211 }
212 hw := md5.New()
213 hw.Write([]byte(hostname))
214 copy(id, hw.Sum(nil))
215 return id
216}
217
218// NewObjectId returns a new unique ObjectId.
219func NewObjectId() ObjectId {
220 var b [12]byte
221 // Timestamp, 4 bytes, big endian
222 binary.BigEndian.PutUint32(b[:], uint32(time.Now().Unix()))
223 // Machine, first 3 bytes of md5(hostname)
224 b[4] = machineId[0]
225 b[5] = machineId[1]
226 b[6] = machineId[2]
227 // Pid, 2 bytes, specs don't specify endianness, but we use big endian.
228 pid := os.Getpid()
229 b[7] = byte(pid >> 8)
230 b[8] = byte(pid)
231 // Increment, 3 bytes, big endian
232 i := atomic.AddUint32(&objectIdCounter, 1)
233 b[9] = byte(i >> 16)
234 b[10] = byte(i >> 8)
235 b[11] = byte(i)
236 return ObjectId(b[:])
237}
238
239// NewObjectIdWithTime returns a dummy ObjectId with the timestamp part filled
240// with the provided number of seconds from epoch UTC, and all other parts
241// filled with zeroes. It's not safe to insert a document with an id generated
242// by this method, it is useful only for queries to find documents with ids
243// generated before or after the specified timestamp.
244func NewObjectIdWithTime(t time.Time) ObjectId {
245 var b [12]byte
246 binary.BigEndian.PutUint32(b[:4], uint32(t.Unix()))
247 return ObjectId(string(b[:]))
248}
249
250// String returns a hex string representation of the id.
251// Example: ObjectIdHex("4d88e15b60f486e428412dc9").
252func (id ObjectId) String() string {
253 return fmt.Sprintf(`ObjectIdHex("%x")`, string(id))
254}
255
256// Hex returns a hex representation of the ObjectId.
257func (id ObjectId) Hex() string {
258 return hex.EncodeToString([]byte(id))
259}
260
261// MarshalJSON turns a bson.ObjectId into a json.Marshaller.
262func (id ObjectId) MarshalJSON() ([]byte, error) {
263 return []byte(fmt.Sprintf(`"%x"`, string(id))), nil
264}
265
266var nullBytes = []byte("null")
267
268// UnmarshalJSON turns *bson.ObjectId into a json.Unmarshaller.
269func (id *ObjectId) UnmarshalJSON(data []byte) error {
270 if len(data) == 2 && data[0] == '"' && data[1] == '"' || bytes.Equal(data, nullBytes) {
271 *id = ""
272 return nil
273 }
274 if len(data) != 26 || data[0] != '"' || data[25] != '"' {
275 return errors.New(fmt.Sprintf("Invalid ObjectId in JSON: %s", string(data)))
276 }
277 var buf [12]byte
278 _, err := hex.Decode(buf[:], data[1:25])
279 if err != nil {
280 return errors.New(fmt.Sprintf("Invalid ObjectId in JSON: %s (%s)", string(data), err))
281 }
282 *id = ObjectId(string(buf[:]))
283 return nil
284}
285
286// Valid returns true if id is valid. A valid id must contain exactly 12 bytes.
287func (id ObjectId) Valid() bool {
288 return len(id) == 12
289}
290
291// byteSlice returns byte slice of id from start to end.
292// Calling this function with an invalid id will cause a runtime panic.
293func (id ObjectId) byteSlice(start, end int) []byte {
294 if len(id) != 12 {
295 panic(fmt.Sprintf("Invalid ObjectId: %q", string(id)))
296 }
297 return []byte(string(id)[start:end])
298}
299
300// Time returns the timestamp part of the id.
301// It's a runtime error to call this method with an invalid id.
302func (id ObjectId) Time() time.Time {
303 // First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch.
304 secs := int64(binary.BigEndian.Uint32(id.byteSlice(0, 4)))
305 return time.Unix(secs, 0)
306}
307
308// Machine returns the 3-byte machine id part of the id.
309// It's a runtime error to call this method with an invalid id.
310func (id ObjectId) Machine() []byte {
311 return id.byteSlice(4, 7)
312}
313
314// Pid returns the process id part of the id.
315// It's a runtime error to call this method with an invalid id.
316func (id ObjectId) Pid() uint16 {
317 return binary.BigEndian.Uint16(id.byteSlice(7, 9))
318}
319
320// Counter returns the incrementing value part of the id.
321// It's a runtime error to call this method with an invalid id.
322func (id ObjectId) Counter() int32 {
323 b := id.byteSlice(9, 12)
324 // Counter is stored as big-endian 3-byte value
325 return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]))
326}
327
328// The Symbol type is similar to a string and is used in languages with a
329// distinct symbol type.
330type Symbol string
331
332// Now returns the current time with millisecond precision. MongoDB stores
333// timestamps with the same precision, so a Time returned from this method
334// will not change after a roundtrip to the database. That's the only reason
335// why this function exists. Using the time.Now function also works fine
336// otherwise.
337func Now() time.Time {
338 return time.Unix(0, time.Now().UnixNano()/1e6*1e6)
339}
340
341// MongoTimestamp is a special internal type used by MongoDB that for some
342// strange reason has its own datatype defined in BSON.
343type MongoTimestamp int64
344
345type orderKey int64
346
347// MaxKey is a special value that compares higher than all other possible BSON
348// values in a MongoDB database.
349var MaxKey = orderKey(1<<63 - 1)
350
351// MinKey is a special value that compares lower than all other possible BSON
352// values in a MongoDB database.
353var MinKey = orderKey(-1 << 63)
354
355type undefined struct{}
356
357// Undefined represents the undefined BSON value.
358var Undefined undefined
359
360// Binary is a representation for non-standard binary values. Any kind should
361// work, but the following are known as of this writing:
362//
363// 0x00 - Generic. This is decoded as []byte(data), not Binary{0x00, data}.
364// 0x01 - Function (!?)
365// 0x02 - Obsolete generic.
366// 0x03 - UUID
367// 0x05 - MD5
368// 0x80 - User defined.
369//
370type Binary struct {
371 Kind byte
372 Data []byte
373}
374
375// RegEx represents a regular expression. The Options field may contain
376// individual characters defining the way in which the pattern should be
377// applied, and must be sorted. Valid options as of this writing are 'i' for
378// case insensitive matching, 'm' for multi-line matching, 'x' for verbose
379// mode, 'l' to make \w, \W, and similar be locale-dependent, 's' for dot-all
380// mode (a '.' matches everything), and 'u' to make \w, \W, and similar match
381// unicode. The value of the Options parameter is not verified before being
382// marshaled into the BSON format.
383type RegEx struct {
384 Pattern string
385 Options string
386}
387
388// JavaScript is a type that holds JavaScript code. If Scope is non-nil, it
389// will be marshaled as a mapping from identifiers to values that may be
390// used when evaluating the provided Code.
391type JavaScript struct {
392 Code string
393 Scope interface{}
394}
395
396// DBPointer refers to a document id in a namespace.
397//
398// This type is deprecated in the BSON specification and should not be used
399// except for backwards compatibility with ancient applications.
400type DBPointer struct {
401 Namespace string
402 Id ObjectId
403}
404
405const initialBufferSize = 64
406
407func handleErr(err *error) {
408 if r := recover(); r != nil {
409 if _, ok := r.(runtime.Error); ok {
410 panic(r)
411 } else if _, ok := r.(externalPanic); ok {
412 panic(r)
413 } else if s, ok := r.(string); ok {
414 *err = errors.New(s)
415 } else if e, ok := r.(error); ok {
416 *err = e
417 } else {
418 panic(r)
419 }
420 }
421}
422
423// Marshal serializes the in value, which may be a map or a struct value.
424// In the case of struct values, only exported fields will be serialized.
425// The lowercased field name is used as the key for each exported field,
426// but this behavior may be changed using the respective field tag.
427// The tag may also contain flags to tweak the marshalling behavior for
428// the field. The tag formats accepted are:
429//
430// "[<key>][,<flag1>[,<flag2>]]"
431//
432// `(...) bson:"[<key>][,<flag1>[,<flag2>]]" (...)`
433//
434// The following flags are currently supported:
435//
436// omitempty Only include the field if it's not set to the zero
437// value for the type or to empty slices or maps.
438//
439// minsize Marshal an int64 value as an int32, if that's feasible
440// while preserving the numeric value.
441//
442// inline Inline the field, which must be a struct or a map,
443// causing all of its fields or keys to be processed as if
444// they were part of the outer struct. For maps, keys must
445// not conflict with the bson keys of other struct fields.
446//
447// Some examples:
448//
449// type T struct {
450// A bool
451// B int "myb"
452// C string "myc,omitempty"
453// D string `bson:",omitempty" json:"jsonkey"`
454// E int64 ",minsize"
455// F int64 "myf,omitempty,minsize"
456// }
457//
458func Marshal(in interface{}) (out []byte, err error) {
459 defer handleErr(&err)
460 e := &encoder{make([]byte, 0, initialBufferSize)}
461 e.addDoc(reflect.ValueOf(in))
462 return e.out, nil
463}
464
465// Unmarshal deserializes data from in into the out value. The out value
466// must be a map, a pointer to a struct, or a pointer to a bson.D value.
467// The lowercased field name is used as the key for each exported field,
468// but this behavior may be changed using the respective field tag.
469// The tag may also contain flags to tweak the marshalling behavior for
470// the field. The tag formats accepted are:
471//
472// "[<key>][,<flag1>[,<flag2>]]"
473//
474// `(...) bson:"[<key>][,<flag1>[,<flag2>]]" (...)`
475//
476// The following flags are currently supported during unmarshal (see the
477// Marshal method for other flags):
478//
479// inline Inline the field, which must be a struct or a map.
480// Inlined structs are handled as if its fields were part
481// of the outer struct. An inlined map causes keys that do
482// not match any other struct field to be inserted in the
483// map rather than being discarded as usual.
484//
485// The target field or element types of out may not necessarily match
486// the BSON values of the provided data. The following conversions are
487// made automatically:
488//
489// - Numeric types are converted if at least the integer part of the
490// value would be preserved correctly
491// - Bools are converted to numeric types as 1 or 0
492// - Numeric types are converted to bools as true if not 0 or false otherwise
493// - Binary and string BSON data is converted to a string, array or byte slice
494//
495// If the value would not fit the type and cannot be converted, it's
496// silently skipped.
497//
498// Pointer values are initialized when necessary.
499func Unmarshal(in []byte, out interface{}) (err error) {
500 if raw, ok := out.(*Raw); ok {
501 raw.Kind = 3
502 raw.Data = in
503 return nil
504 }
505 defer handleErr(&err)
506 v := reflect.ValueOf(out)
507 switch v.Kind() {
508 case reflect.Ptr:
509 fallthrough
510 case reflect.Map:
511 d := newDecoder(in)
512 d.readDocTo(v)
513 case reflect.Struct:
514 return errors.New("Unmarshal can't deal with struct values. Use a pointer.")
515 default:
516 return errors.New("Unmarshal needs a map or a pointer to a struct.")
517 }
518 return nil
519}
520
521// Unmarshal deserializes raw into the out value. If the out value type
522// is not compatible with raw, a *bson.TypeError is returned.
523//
524// See the Unmarshal function documentation for more details on the
525// unmarshalling process.
526func (raw Raw) Unmarshal(out interface{}) (err error) {
527 defer handleErr(&err)
528 v := reflect.ValueOf(out)
529 switch v.Kind() {
530 case reflect.Ptr:
531 v = v.Elem()
532 fallthrough
533 case reflect.Map:
534 d := newDecoder(raw.Data)
535 good := d.readElemTo(v, raw.Kind)
536 if !good {
537 return &TypeError{v.Type(), raw.Kind}
538 }
539 case reflect.Struct:
540 return errors.New("Raw Unmarshal can't deal with struct values. Use a pointer.")
541 default:
542 return errors.New("Raw Unmarshal needs a map or a valid pointer.")
543 }
544 return nil
545}
546
547type TypeError struct {
548 Type reflect.Type
549 Kind byte
550}
551
552func (e *TypeError) Error() string {
553 return fmt.Sprintf("BSON kind 0x%02x isn't compatible with type %s", e.Kind, e.Type.String())
554}
555
556// --------------------------------------------------------------------------
557// Maintain a mapping of keys to structure field indexes
558
559type structInfo struct {
560 FieldsMap map[string]fieldInfo
561 FieldsList []fieldInfo
562 InlineMap int
563 Zero reflect.Value
564}
565
566type fieldInfo struct {
567 Key string
568 Num int
569 OmitEmpty bool
570 MinSize bool
571 Inline []int
572}
573
574var structMap = make(map[reflect.Type]*structInfo)
575var structMapMutex sync.RWMutex
576
577type externalPanic string
578
579func (e externalPanic) String() string {
580 return string(e)
581}
582
583func getStructInfo(st reflect.Type) (*structInfo, error) {
584 structMapMutex.RLock()
585 sinfo, found := structMap[st]
586 structMapMutex.RUnlock()
587 if found {
588 return sinfo, nil
589 }
590 n := st.NumField()
591 fieldsMap := make(map[string]fieldInfo)
592 fieldsList := make([]fieldInfo, 0, n)
593 inlineMap := -1
594 for i := 0; i != n; i++ {
595 field := st.Field(i)
596 if field.PkgPath != "" {
597 continue // Private field
598 }
599
600 info := fieldInfo{Num: i}
601
602 tag := field.Tag.Get("bson")
603 if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
604 tag = string(field.Tag)
605 }
606 if tag == "-" {
607 continue
608 }
609
610 // XXX Drop this after a few releases.
611 if s := strings.Index(tag, "/"); s >= 0 {
612 recommend := tag[:s]
613 for _, c := range tag[s+1:] {
614 switch c {
615 case 'c':
616 recommend += ",omitempty"
617 case 's':
618 recommend += ",minsize"
619 default:
620 msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", string([]byte{uint8(c)}), tag, st)
621 panic(externalPanic(msg))
622 }
623 }
624 msg := fmt.Sprintf("Replace tag %q in field %s of type %s by %q", tag, field.Name, st, recommend)
625 panic(externalPanic(msg))
626 }
627
628 inline := false
629 fields := strings.Split(tag, ",")
630 if len(fields) > 1 {
631 for _, flag := range fields[1:] {
632 switch flag {
633 case "omitempty":
634 info.OmitEmpty = true
635 case "minsize":
636 info.MinSize = true
637 case "inline":
638 inline = true
639 default:
640 msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)
641 panic(externalPanic(msg))
642 }
643 }
644 tag = fields[0]
645 }
646
647 if inline {
648 switch field.Type.Kind() {
649 case reflect.Map:
650 if inlineMap >= 0 {
651 return nil, errors.New("Multiple ,inline maps in struct " + st.String())
652 }
653 if field.Type.Key() != reflect.TypeOf("") {
654 return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
655 }
656 inlineMap = info.Num
657 case reflect.Struct:
658 sinfo, err := getStructInfo(field.Type)
659 if err != nil {
660 return nil, err
661 }
662 for _, finfo := range sinfo.FieldsList {
663 if _, found := fieldsMap[finfo.Key]; found {
664 msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
665 return nil, errors.New(msg)
666 }
667 if finfo.Inline == nil {
668 finfo.Inline = []int{i, finfo.Num}
669 } else {
670 finfo.Inline = append([]int{i}, finfo.Inline...)
671 }
672 fieldsMap[finfo.Key] = finfo
673 fieldsList = append(fieldsList, finfo)
674 }
675 default:
676 panic("Option ,inline needs a struct value or map field")
677 }
678 continue
679 }
680
681 if tag != "" {
682 info.Key = tag
683 } else {
684 info.Key = strings.ToLower(field.Name)
685 }
686
687 if _, found = fieldsMap[info.Key]; found {
688 msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
689 return nil, errors.New(msg)
690 }
691
692 fieldsList = append(fieldsList, info)
693 fieldsMap[info.Key] = info
694 }
695 sinfo = &structInfo{
696 fieldsMap,
697 fieldsList,
698 inlineMap,
699 reflect.New(st).Elem(),
700 }
701 structMapMutex.Lock()
702 structMap[st] = sinfo
703 structMapMutex.Unlock()
704 return sinfo, nil
705}
vendor/gopkg.in/mgo.v2/bson/bson_test.go+1605−0
@@ -0,0 +1,1605 @@
1// BSON library for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26// gobson - BSON library for Go.
27
28package bson_test
29
30import (
31 "encoding/binary"
32 "encoding/json"
33 "errors"
34 "net/url"
35 "reflect"
36 "testing"
37 "time"
38
39 . "gopkg.in/check.v1"
40 "gopkg.in/mgo.v2/bson"
41)
42
43func TestAll(t *testing.T) {
44 TestingT(t)
45}
46
47type S struct{}
48
49var _ = Suite(&S{})
50
51// Wrap up the document elements contained in data, prepending the int32
52// length of the data, and appending the '\x00' value closing the document.
53func wrapInDoc(data string) string {
54 result := make([]byte, len(data)+5)
55 binary.LittleEndian.PutUint32(result, uint32(len(result)))
56 copy(result[4:], []byte(data))
57 return string(result)
58}
59
60func makeZeroDoc(value interface{}) (zero interface{}) {
61 v := reflect.ValueOf(value)
62 t := v.Type()
63 switch t.Kind() {
64 case reflect.Map:
65 mv := reflect.MakeMap(t)
66 zero = mv.Interface()
67 case reflect.Ptr:
68 pv := reflect.New(v.Type().Elem())
69 zero = pv.Interface()
70 case reflect.Slice, reflect.Int:
71 zero = reflect.New(t).Interface()
72 default:
73 panic("unsupported doc type")
74 }
75 return zero
76}
77
78func testUnmarshal(c *C, data string, obj interface{}) {
79 zero := makeZeroDoc(obj)
80 err := bson.Unmarshal([]byte(data), zero)
81 c.Assert(err, IsNil)
82 c.Assert(zero, DeepEquals, obj)
83}
84
85type testItemType struct {
86 obj interface{}
87 data string
88}
89
90// --------------------------------------------------------------------------
91// Samples from bsonspec.org:
92
93var sampleItems = []testItemType{
94 {bson.M{"hello": "world"},
95 "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00"},
96 {bson.M{"BSON": []interface{}{"awesome", float64(5.05), 1986}},
97 "1\x00\x00\x00\x04BSON\x00&\x00\x00\x00\x020\x00\x08\x00\x00\x00" +
98 "awesome\x00\x011\x00333333\x14@\x102\x00\xc2\x07\x00\x00\x00\x00"},
99}
100
101func (s *S) TestMarshalSampleItems(c *C) {
102 for i, item := range sampleItems {
103 data, err := bson.Marshal(item.obj)
104 c.Assert(err, IsNil)
105 c.Assert(string(data), Equals, item.data, Commentf("Failed on item %d", i))
106 }
107}
108
109func (s *S) TestUnmarshalSampleItems(c *C) {
110 for i, item := range sampleItems {
111 value := bson.M{}
112 err := bson.Unmarshal([]byte(item.data), value)
113 c.Assert(err, IsNil)
114 c.Assert(value, DeepEquals, item.obj, Commentf("Failed on item %d", i))
115 }
116}
117
118// --------------------------------------------------------------------------
119// Every type, ordered by the type flag. These are not wrapped with the
120// length and last \x00 from the document. wrapInDoc() computes them.
121// Note that all of them should be supported as two-way conversions.
122
123var allItems = []testItemType{
124 {bson.M{},
125 ""},
126 {bson.M{"_": float64(5.05)},
127 "\x01_\x00333333\x14@"},
128 {bson.M{"_": "yo"},
129 "\x02_\x00\x03\x00\x00\x00yo\x00"},
130 {bson.M{"_": bson.M{"a": true}},
131 "\x03_\x00\x09\x00\x00\x00\x08a\x00\x01\x00"},
132 {bson.M{"_": []interface{}{true, false}},
133 "\x04_\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00"},
134 {bson.M{"_": []byte("yo")},
135 "\x05_\x00\x02\x00\x00\x00\x00yo"},
136 {bson.M{"_": bson.Binary{0x80, []byte("udef")}},
137 "\x05_\x00\x04\x00\x00\x00\x80udef"},
138 {bson.M{"_": bson.Undefined}, // Obsolete, but still seen in the wild.
139 "\x06_\x00"},
140 {bson.M{"_": bson.ObjectId("0123456789ab")},
141 "\x07_\x000123456789ab"},
142 {bson.M{"_": bson.DBPointer{"testnamespace", bson.ObjectId("0123456789ab")}},
143 "\x0C_\x00\x0e\x00\x00\x00testnamespace\x000123456789ab"},
144 {bson.M{"_": false},
145 "\x08_\x00\x00"},
146 {bson.M{"_": true},
147 "\x08_\x00\x01"},
148 {bson.M{"_": time.Unix(0, 258e6)}, // Note the NS <=> MS conversion.
149 "\x09_\x00\x02\x01\x00\x00\x00\x00\x00\x00"},
150 {bson.M{"_": nil},
151 "\x0A_\x00"},
152 {bson.M{"_": bson.RegEx{"ab", "cd"}},
153 "\x0B_\x00ab\x00cd\x00"},
154 {bson.M{"_": bson.JavaScript{"code", nil}},
155 "\x0D_\x00\x05\x00\x00\x00code\x00"},
156 {bson.M{"_": bson.Symbol("sym")},
157 "\x0E_\x00\x04\x00\x00\x00sym\x00"},
158 {bson.M{"_": bson.JavaScript{"code", bson.M{"": nil}}},
159 "\x0F_\x00\x14\x00\x00\x00\x05\x00\x00\x00code\x00" +
160 "\x07\x00\x00\x00\x0A\x00\x00"},
161 {bson.M{"_": 258},
162 "\x10_\x00\x02\x01\x00\x00"},
163 {bson.M{"_": bson.MongoTimestamp(258)},
164 "\x11_\x00\x02\x01\x00\x00\x00\x00\x00\x00"},
165 {bson.M{"_": int64(258)},
166 "\x12_\x00\x02\x01\x00\x00\x00\x00\x00\x00"},
167 {bson.M{"_": int64(258 << 32)},
168 "\x12_\x00\x00\x00\x00\x00\x02\x01\x00\x00"},
169 {bson.M{"_": bson.MaxKey},
170 "\x7F_\x00"},
171 {bson.M{"_": bson.MinKey},
172 "\xFF_\x00"},
173}
174
175func (s *S) TestMarshalAllItems(c *C) {
176 for i, item := range allItems {
177 data, err := bson.Marshal(item.obj)
178 c.Assert(err, IsNil)
179 c.Assert(string(data), Equals, wrapInDoc(item.data), Commentf("Failed on item %d: %#v", i, item))
180 }
181}
182
183func (s *S) TestUnmarshalAllItems(c *C) {
184 for i, item := range allItems {
185 value := bson.M{}
186 err := bson.Unmarshal([]byte(wrapInDoc(item.data)), value)
187 c.Assert(err, IsNil)
188 c.Assert(value, DeepEquals, item.obj, Commentf("Failed on item %d: %#v", i, item))
189 }
190}
191
192func (s *S) TestUnmarshalRawAllItems(c *C) {
193 for i, item := range allItems {
194 if len(item.data) == 0 {
195 continue
196 }
197 value := item.obj.(bson.M)["_"]
198 if value == nil {
199 continue
200 }
201 pv := reflect.New(reflect.ValueOf(value).Type())
202 raw := bson.Raw{item.data[0], []byte(item.data[3:])}
203 c.Logf("Unmarshal raw: %#v, %#v", raw, pv.Interface())
204 err := raw.Unmarshal(pv.Interface())
205 c.Assert(err, IsNil)
206 c.Assert(pv.Elem().Interface(), DeepEquals, value, Commentf("Failed on item %d: %#v", i, item))
207 }
208}
209
210func (s *S) TestUnmarshalRawIncompatible(c *C) {
211 raw := bson.Raw{0x08, []byte{0x01}} // true
212 err := raw.Unmarshal(&struct{}{})
213 c.Assert(err, ErrorMatches, "BSON kind 0x08 isn't compatible with type struct \\{\\}")
214}
215
216func (s *S) TestUnmarshalZeroesStruct(c *C) {
217 data, err := bson.Marshal(bson.M{"b": 2})
218 c.Assert(err, IsNil)
219 type T struct{ A, B int }
220 v := T{A: 1}
221 err = bson.Unmarshal(data, &v)
222 c.Assert(err, IsNil)
223 c.Assert(v.A, Equals, 0)
224 c.Assert(v.B, Equals, 2)
225}
226
227func (s *S) TestUnmarshalZeroesMap(c *C) {
228 data, err := bson.Marshal(bson.M{"b": 2})
229 c.Assert(err, IsNil)
230 m := bson.M{"a": 1}
231 err = bson.Unmarshal(data, &m)
232 c.Assert(err, IsNil)
233 c.Assert(m, DeepEquals, bson.M{"b": 2})
234}
235
236func (s *S) TestUnmarshalNonNilInterface(c *C) {
237 data, err := bson.Marshal(bson.M{"b": 2})
238 c.Assert(err, IsNil)
239 m := bson.M{"a": 1}
240 var i interface{}
241 i = m
242 err = bson.Unmarshal(data, &i)
243 c.Assert(err, IsNil)
244 c.Assert(i, DeepEquals, bson.M{"b": 2})
245 c.Assert(m, DeepEquals, bson.M{"a": 1})
246}
247
248// --------------------------------------------------------------------------
249// Some one way marshaling operations which would unmarshal differently.
250
251var oneWayMarshalItems = []testItemType{
252 // These are being passed as pointers, and will unmarshal as values.
253 {bson.M{"": &bson.Binary{0x02, []byte("old")}},
254 "\x05\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old"},
255 {bson.M{"": &bson.Binary{0x80, []byte("udef")}},
256 "\x05\x00\x04\x00\x00\x00\x80udef"},
257 {bson.M{"": &bson.RegEx{"ab", "cd"}},
258 "\x0B\x00ab\x00cd\x00"},
259 {bson.M{"": &bson.JavaScript{"code", nil}},
260 "\x0D\x00\x05\x00\x00\x00code\x00"},
261 {bson.M{"": &bson.JavaScript{"code", bson.M{"": nil}}},
262 "\x0F\x00\x14\x00\x00\x00\x05\x00\x00\x00code\x00" +
263 "\x07\x00\x00\x00\x0A\x00\x00"},
264
265 // There's no float32 type in BSON. Will encode as a float64.
266 {bson.M{"": float32(5.05)},
267 "\x01\x00\x00\x00\x00@33\x14@"},
268
269 // The array will be unmarshaled as a slice instead.
270 {bson.M{"": [2]bool{true, false}},
271 "\x04\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00"},
272
273 // The typed slice will be unmarshaled as []interface{}.
274 {bson.M{"": []bool{true, false}},
275 "\x04\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00"},
276
277 // Will unmarshal as a []byte.
278 {bson.M{"": bson.Binary{0x00, []byte("yo")}},
279 "\x05\x00\x02\x00\x00\x00\x00yo"},
280 {bson.M{"": bson.Binary{0x02, []byte("old")}},
281 "\x05\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old"},
282
283 // No way to preserve the type information here. We might encode as a zero
284 // value, but this would mean that pointer values in structs wouldn't be
285 // able to correctly distinguish between unset and set to the zero value.
286 {bson.M{"": (*byte)(nil)},
287 "\x0A\x00"},
288
289 // No int types smaller than int32 in BSON. Could encode this as a char,
290 // but it would still be ambiguous, take more, and be awkward in Go when
291 // loaded without typing information.
292 {bson.M{"": byte(8)},
293 "\x10\x00\x08\x00\x00\x00"},
294
295 // There are no unsigned types in BSON. Will unmarshal as int32 or int64.
296 {bson.M{"": uint32(258)},
297 "\x10\x00\x02\x01\x00\x00"},
298 {bson.M{"": uint64(258)},
299 "\x12\x00\x02\x01\x00\x00\x00\x00\x00\x00"},
300 {bson.M{"": uint64(258 << 32)},
301 "\x12\x00\x00\x00\x00\x00\x02\x01\x00\x00"},
302
303 // This will unmarshal as int.
304 {bson.M{"": int32(258)},
305 "\x10\x00\x02\x01\x00\x00"},
306
307 // That's a special case. The unsigned value is too large for an int32,
308 // so an int64 is used instead.
309 {bson.M{"": uint32(1<<32 - 1)},
310 "\x12\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00"},
311 {bson.M{"": uint(1<<32 - 1)},
312 "\x12\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00"},
313}
314
315func (s *S) TestOneWayMarshalItems(c *C) {
316 for i, item := range oneWayMarshalItems {
317 data, err := bson.Marshal(item.obj)
318 c.Assert(err, IsNil)
319 c.Assert(string(data), Equals, wrapInDoc(item.data),
320 Commentf("Failed on item %d", i))
321 }
322}
323
324// --------------------------------------------------------------------------
325// Two-way tests for user-defined structures using the samples
326// from bsonspec.org.
327
328type specSample1 struct {
329 Hello string
330}
331
332type specSample2 struct {
333 BSON []interface{} "BSON"
334}
335
336var structSampleItems = []testItemType{
337 {&specSample1{"world"},
338 "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00"},
339 {&specSample2{[]interface{}{"awesome", float64(5.05), 1986}},
340 "1\x00\x00\x00\x04BSON\x00&\x00\x00\x00\x020\x00\x08\x00\x00\x00" +
341 "awesome\x00\x011\x00333333\x14@\x102\x00\xc2\x07\x00\x00\x00\x00"},
342}
343
344func (s *S) TestMarshalStructSampleItems(c *C) {
345 for i, item := range structSampleItems {
346 data, err := bson.Marshal(item.obj)
347 c.Assert(err, IsNil)
348 c.Assert(string(data), Equals, item.data,
349 Commentf("Failed on item %d", i))
350 }
351}
352
353func (s *S) TestUnmarshalStructSampleItems(c *C) {
354 for _, item := range structSampleItems {
355 testUnmarshal(c, item.data, item.obj)
356 }
357}
358
359func (s *S) Test64bitInt(c *C) {
360 var i int64 = (1 << 31)
361 if int(i) > 0 {
362 data, err := bson.Marshal(bson.M{"i": int(i)})
363 c.Assert(err, IsNil)
364 c.Assert(string(data), Equals, wrapInDoc("\x12i\x00\x00\x00\x00\x80\x00\x00\x00\x00"))
365
366 var result struct{ I int }
367 err = bson.Unmarshal(data, &result)
368 c.Assert(err, IsNil)
369 c.Assert(int64(result.I), Equals, i)
370 }
371}
372
373// --------------------------------------------------------------------------
374// Generic two-way struct marshaling tests.
375
376var bytevar = byte(8)
377var byteptr = &bytevar
378
379var structItems = []testItemType{
380 {&struct{ Ptr *byte }{nil},
381 "\x0Aptr\x00"},
382 {&struct{ Ptr *byte }{&bytevar},
383 "\x10ptr\x00\x08\x00\x00\x00"},
384 {&struct{ Ptr **byte }{&byteptr},
385 "\x10ptr\x00\x08\x00\x00\x00"},
386 {&struct{ Byte byte }{8},
387 "\x10byte\x00\x08\x00\x00\x00"},
388 {&struct{ Byte byte }{0},
389 "\x10byte\x00\x00\x00\x00\x00"},
390 {&struct {
391 V byte "Tag"
392 }{8},
393 "\x10Tag\x00\x08\x00\x00\x00"},
394 {&struct {
395 V *struct {
396 Byte byte
397 }
398 }{&struct{ Byte byte }{8}},
399 "\x03v\x00" + "\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00"},
400 {&struct{ priv byte }{}, ""},
401
402 // The order of the dumped fields should be the same in the struct.
403 {&struct{ A, C, B, D, F, E *byte }{},
404 "\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x0Ae\x00"},
405
406 {&struct{ V bson.Raw }{bson.Raw{0x03, []byte("\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00")}},
407 "\x03v\x00" + "\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00"},
408 {&struct{ V bson.Raw }{bson.Raw{0x10, []byte("\x00\x00\x00\x00")}},
409 "\x10v\x00" + "\x00\x00\x00\x00"},
410
411 // Byte arrays.
412 {&struct{ V [2]byte }{[2]byte{'y', 'o'}},
413 "\x05v\x00\x02\x00\x00\x00\x00yo"},
414}
415
416func (s *S) TestMarshalStructItems(c *C) {
417 for i, item := range structItems {
418 data, err := bson.Marshal(item.obj)
419 c.Assert(err, IsNil)
420 c.Assert(string(data), Equals, wrapInDoc(item.data),
421 Commentf("Failed on item %d", i))
422 }
423}
424
425func (s *S) TestUnmarshalStructItems(c *C) {
426 for _, item := range structItems {
427 testUnmarshal(c, wrapInDoc(item.data), item.obj)
428 }
429}
430
431func (s *S) TestUnmarshalRawStructItems(c *C) {
432 for i, item := range structItems {
433 raw := bson.Raw{0x03, []byte(wrapInDoc(item.data))}
434 zero := makeZeroDoc(item.obj)
435 err := raw.Unmarshal(zero)
436 c.Assert(err, IsNil)
437 c.Assert(zero, DeepEquals, item.obj, Commentf("Failed on item %d: %#v", i, item))
438 }
439}
440
441func (s *S) TestUnmarshalRawNil(c *C) {
442 // Regression test: shouldn't try to nil out the pointer itself,
443 // as it's not settable.
444 raw := bson.Raw{0x0A, []byte{}}
445 err := raw.Unmarshal(&struct{}{})
446 c.Assert(err, IsNil)
447}
448
449// --------------------------------------------------------------------------
450// One-way marshaling tests.
451
452type dOnIface struct {
453 D interface{}
454}
455
456type ignoreField struct {
457 Before string
458 Ignore string `bson:"-"`
459 After string
460}
461
462var marshalItems = []testItemType{
463 // Ordered document dump. Will unmarshal as a dictionary by default.
464 {bson.D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", nil}, {"f", nil}, {"e", true}},
465 "\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x08e\x00\x01"},
466 {MyD{{"a", nil}, {"c", nil}, {"b", nil}, {"d", nil}, {"f", nil}, {"e", true}},
467 "\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x08e\x00\x01"},
468 {&dOnIface{bson.D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", true}}},
469 "\x03d\x00" + wrapInDoc("\x0Aa\x00\x0Ac\x00\x0Ab\x00\x08d\x00\x01")},
470
471 {bson.RawD{{"a", bson.Raw{0x0A, nil}}, {"c", bson.Raw{0x0A, nil}}, {"b", bson.Raw{0x08, []byte{0x01}}}},
472 "\x0Aa\x00" + "\x0Ac\x00" + "\x08b\x00\x01"},
473 {MyRawD{{"a", bson.Raw{0x0A, nil}}, {"c", bson.Raw{0x0A, nil}}, {"b", bson.Raw{0x08, []byte{0x01}}}},
474 "\x0Aa\x00" + "\x0Ac\x00" + "\x08b\x00\x01"},
475 {&dOnIface{bson.RawD{{"a", bson.Raw{0x0A, nil}}, {"c", bson.Raw{0x0A, nil}}, {"b", bson.Raw{0x08, []byte{0x01}}}}},
476 "\x03d\x00" + wrapInDoc("\x0Aa\x00"+"\x0Ac\x00"+"\x08b\x00\x01")},
477
478 {&ignoreField{"before", "ignore", "after"},
479 "\x02before\x00\a\x00\x00\x00before\x00\x02after\x00\x06\x00\x00\x00after\x00"},
480
481 // Marshalling a Raw document does nothing.
482 {bson.Raw{0x03, []byte(wrapInDoc("anything"))},
483 "anything"},
484 {bson.Raw{Data: []byte(wrapInDoc("anything"))},
485 "anything"},
486}
487
488func (s *S) TestMarshalOneWayItems(c *C) {
489 for _, item := range marshalItems {
490 data, err := bson.Marshal(item.obj)
491 c.Assert(err, IsNil)
492 c.Assert(string(data), Equals, wrapInDoc(item.data))
493 }
494}
495
496// --------------------------------------------------------------------------
497// One-way unmarshaling tests.
498
499var unmarshalItems = []testItemType{
500 // Field is private. Should not attempt to unmarshal it.
501 {&struct{ priv byte }{},
502 "\x10priv\x00\x08\x00\x00\x00"},
503
504 // Wrong casing. Field names are lowercased.
505 {&struct{ Byte byte }{},
506 "\x10Byte\x00\x08\x00\x00\x00"},
507
508 // Ignore non-existing field.
509 {&struct{ Byte byte }{9},
510 "\x10boot\x00\x08\x00\x00\x00" + "\x10byte\x00\x09\x00\x00\x00"},
511
512 // Do not unmarshal on ignored field.
513 {&ignoreField{"before", "", "after"},
514 "\x02before\x00\a\x00\x00\x00before\x00" +
515 "\x02-\x00\a\x00\x00\x00ignore\x00" +
516 "\x02after\x00\x06\x00\x00\x00after\x00"},
517
518 // Ignore unsuitable types silently.
519 {map[string]string{"str": "s"},
520 "\x02str\x00\x02\x00\x00\x00s\x00" + "\x10int\x00\x01\x00\x00\x00"},
521 {map[string][]int{"array": []int{5, 9}},
522 "\x04array\x00" + wrapInDoc("\x100\x00\x05\x00\x00\x00"+"\x021\x00\x02\x00\x00\x00s\x00"+"\x102\x00\x09\x00\x00\x00")},
523
524 // Wrong type. Shouldn't init pointer.
525 {&struct{ Str *byte }{},
526 "\x02str\x00\x02\x00\x00\x00s\x00"},
527 {&struct{ Str *struct{ Str string } }{},
528 "\x02str\x00\x02\x00\x00\x00s\x00"},
529
530 // Ordered document.
531 {&struct{ bson.D }{bson.D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", true}}},
532 "\x03d\x00" + wrapInDoc("\x0Aa\x00\x0Ac\x00\x0Ab\x00\x08d\x00\x01")},
533
534 // Raw document.
535 {&bson.Raw{0x03, []byte(wrapInDoc("\x10byte\x00\x08\x00\x00\x00"))},
536 "\x10byte\x00\x08\x00\x00\x00"},
537
538 // RawD document.
539 {&struct{ bson.RawD }{bson.RawD{{"a", bson.Raw{0x0A, []byte{}}}, {"c", bson.Raw{0x0A, []byte{}}}, {"b", bson.Raw{0x08, []byte{0x01}}}}},
540 "\x03rawd\x00" + wrapInDoc("\x0Aa\x00\x0Ac\x00\x08b\x00\x01")},
541
542 // Decode old binary.
543 {bson.M{"_": []byte("old")},
544 "\x05_\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old"},
545
546 // Decode old binary without length. According to the spec, this shouldn't happen.
547 {bson.M{"_": []byte("old")},
548 "\x05_\x00\x03\x00\x00\x00\x02old"},
549}
550
551func (s *S) TestUnmarshalOneWayItems(c *C) {
552 for _, item := range unmarshalItems {
553 testUnmarshal(c, wrapInDoc(item.data), item.obj)
554 }
555}
556
557func (s *S) TestUnmarshalNilInStruct(c *C) {
558 // Nil is the default value, so we need to ensure it's indeed being set.
559 b := byte(1)
560 v := &struct{ Ptr *byte }{&b}
561 err := bson.Unmarshal([]byte(wrapInDoc("\x0Aptr\x00")), v)
562 c.Assert(err, IsNil)
563 c.Assert(v, DeepEquals, &struct{ Ptr *byte }{nil})
564}
565
566// --------------------------------------------------------------------------
567// Marshalling error cases.
568
569type structWithDupKeys struct {
570 Name byte
571 Other byte "name" // Tag should precede.
572}
573
574var marshalErrorItems = []testItemType{
575 {bson.M{"": uint64(1 << 63)},
576 "BSON has no uint64 type, and value is too large to fit correctly in an int64"},
577 {bson.M{"": bson.ObjectId("tooshort")},
578 "ObjectIDs must be exactly 12 bytes long \\(got 8\\)"},
579 {int64(123),
580 "Can't marshal int64 as a BSON document"},
581 {bson.M{"": 1i},
582 "Can't marshal complex128 in a BSON document"},
583 {&structWithDupKeys{},
584 "Duplicated key 'name' in struct bson_test.structWithDupKeys"},
585 {bson.Raw{0x0A, []byte{}},
586 "Attempted to unmarshal Raw kind 10 as a document"},
587 {&inlineCantPtr{&struct{ A, B int }{1, 2}},
588 "Option ,inline needs a struct value or map field"},
589 {&inlineDupName{1, struct{ A, B int }{2, 3}},
590 "Duplicated key 'a' in struct bson_test.inlineDupName"},
591 {&inlineDupMap{},
592 "Multiple ,inline maps in struct bson_test.inlineDupMap"},
593 {&inlineBadKeyMap{},
594 "Option ,inline needs a map with string keys in struct bson_test.inlineBadKeyMap"},
595 {&inlineMap{A: 1, M: map[string]interface{}{"a": 1}},
596 `Can't have key "a" in inlined map; conflicts with struct field`},
597}
598
599func (s *S) TestMarshalErrorItems(c *C) {
600 for _, item := range marshalErrorItems {
601 data, err := bson.Marshal(item.obj)
602 c.Assert(err, ErrorMatches, item.data)
603 c.Assert(data, IsNil)
604 }
605}
606
607// --------------------------------------------------------------------------
608// Unmarshalling error cases.
609
610type unmarshalErrorType struct {
611 obj interface{}
612 data string
613 error string
614}
615
616var unmarshalErrorItems = []unmarshalErrorType{
617 // Tag name conflicts with existing parameter.
618 {&structWithDupKeys{},
619 "\x10name\x00\x08\x00\x00\x00",
620 "Duplicated key 'name' in struct bson_test.structWithDupKeys"},
621
622 // Non-string map key.
623 {map[int]interface{}{},
624 "\x10name\x00\x08\x00\x00\x00",
625 "BSON map must have string keys. Got: map\\[int\\]interface \\{\\}"},
626
627 {nil,
628 "\xEEname\x00",
629 "Unknown element kind \\(0xEE\\)"},
630
631 {struct{ Name bool }{},
632 "\x10name\x00\x08\x00\x00\x00",
633 "Unmarshal can't deal with struct values. Use a pointer."},
634
635 {123,
636 "\x10name\x00\x08\x00\x00\x00",
637 "Unmarshal needs a map or a pointer to a struct."},
638}
639
640func (s *S) TestUnmarshalErrorItems(c *C) {
641 for _, item := range unmarshalErrorItems {
642 data := []byte(wrapInDoc(item.data))
643 var value interface{}
644 switch reflect.ValueOf(item.obj).Kind() {
645 case reflect.Map, reflect.Ptr:
646 value = makeZeroDoc(item.obj)
647 case reflect.Invalid:
648 value = bson.M{}
649 default:
650 value = item.obj
651 }
652 err := bson.Unmarshal(data, value)
653 c.Assert(err, ErrorMatches, item.error)
654 }
655}
656
657type unmarshalRawErrorType struct {
658 obj interface{}
659 raw bson.Raw
660 error string
661}
662
663var unmarshalRawErrorItems = []unmarshalRawErrorType{
664 // Tag name conflicts with existing parameter.
665 {&structWithDupKeys{},
666 bson.Raw{0x03, []byte("\x10byte\x00\x08\x00\x00\x00")},
667 "Duplicated key 'name' in struct bson_test.structWithDupKeys"},
668
669 {&struct{}{},
670 bson.Raw{0xEE, []byte{}},
671 "Unknown element kind \\(0xEE\\)"},
672
673 {struct{ Name bool }{},
674 bson.Raw{0x10, []byte("\x08\x00\x00\x00")},
675 "Raw Unmarshal can't deal with struct values. Use a pointer."},
676
677 {123,
678 bson.Raw{0x10, []byte("\x08\x00\x00\x00")},
679 "Raw Unmarshal needs a map or a valid pointer."},
680}
681
682func (s *S) TestUnmarshalRawErrorItems(c *C) {
683 for i, item := range unmarshalRawErrorItems {
684 err := item.raw.Unmarshal(item.obj)
685 c.Assert(err, ErrorMatches, item.error, Commentf("Failed on item %d: %#v\n", i, item))
686 }
687}
688
689var corruptedData = []string{
690 "\x04\x00\x00\x00\x00", // Shorter than minimum
691 "\x06\x00\x00\x00\x00", // Not enough data
692 "\x05\x00\x00", // Broken length
693 "\x05\x00\x00\x00\xff", // Corrupted termination
694 "\x0A\x00\x00\x00\x0Aooop\x00", // Unfinished C string
695
696 // Array end past end of string (s[2]=0x07 is correct)
697 wrapInDoc("\x04\x00\x09\x00\x00\x00\x0A\x00\x00"),
698
699 // Array end within string, but past acceptable.
700 wrapInDoc("\x04\x00\x08\x00\x00\x00\x0A\x00\x00"),
701
702 // Document end within string, but past acceptable.
703 wrapInDoc("\x03\x00\x08\x00\x00\x00\x0A\x00\x00"),
704
705 // String with corrupted end.
706 wrapInDoc("\x02\x00\x03\x00\x00\x00yo\xFF"),
707}
708
709func (s *S) TestUnmarshalMapDocumentTooShort(c *C) {
710 for _, data := range corruptedData {
711 err := bson.Unmarshal([]byte(data), bson.M{})
712 c.Assert(err, ErrorMatches, "Document is corrupted")
713
714 err = bson.Unmarshal([]byte(data), &struct{}{})
715 c.Assert(err, ErrorMatches, "Document is corrupted")
716 }
717}
718
719// --------------------------------------------------------------------------
720// Setter test cases.
721
722var setterResult = map[string]error{}
723
724type setterType struct {
725 received interface{}
726}
727
728func (o *setterType) SetBSON(raw bson.Raw) error {
729 err := raw.Unmarshal(&o.received)
730 if err != nil {
731 panic("The panic:" + err.Error())
732 }
733 if s, ok := o.received.(string); ok {
734 if result, ok := setterResult[s]; ok {
735 return result
736 }
737 }
738 return nil
739}
740
741type ptrSetterDoc struct {
742 Field *setterType "_"
743}
744
745type valSetterDoc struct {
746 Field setterType "_"
747}
748
749func (s *S) TestUnmarshalAllItemsWithPtrSetter(c *C) {
750 for _, item := range allItems {
751 for i := 0; i != 2; i++ {
752 var field *setterType
753 if i == 0 {
754 obj := &ptrSetterDoc{}
755 err := bson.Unmarshal([]byte(wrapInDoc(item.data)), obj)
756 c.Assert(err, IsNil)
757 field = obj.Field
758 } else {
759 obj := &valSetterDoc{}
760 err := bson.Unmarshal([]byte(wrapInDoc(item.data)), obj)
761 c.Assert(err, IsNil)
762 field = &obj.Field
763 }
764 if item.data == "" {
765 // Nothing to unmarshal. Should be untouched.
766 if i == 0 {
767 c.Assert(field, IsNil)
768 } else {
769 c.Assert(field.received, IsNil)
770 }
771 } else {
772 expected := item.obj.(bson.M)["_"]
773 c.Assert(field, NotNil, Commentf("Pointer not initialized (%#v)", expected))
774 c.Assert(field.received, DeepEquals, expected)
775 }
776 }
777 }
778}
779
780func (s *S) TestUnmarshalWholeDocumentWithSetter(c *C) {
781 obj := &setterType{}
782 err := bson.Unmarshal([]byte(sampleItems[0].data), obj)
783 c.Assert(err, IsNil)
784 c.Assert(obj.received, DeepEquals, bson.M{"hello": "world"})
785}
786
787func (s *S) TestUnmarshalSetterOmits(c *C) {
788 setterResult["2"] = &bson.TypeError{}
789 setterResult["4"] = &bson.TypeError{}
790 defer func() {
791 delete(setterResult, "2")
792 delete(setterResult, "4")
793 }()
794
795 m := map[string]*setterType{}
796 data := wrapInDoc("\x02abc\x00\x02\x00\x00\x001\x00" +
797 "\x02def\x00\x02\x00\x00\x002\x00" +
798 "\x02ghi\x00\x02\x00\x00\x003\x00" +
799 "\x02jkl\x00\x02\x00\x00\x004\x00")
800 err := bson.Unmarshal([]byte(data), m)
801 c.Assert(err, IsNil)
802 c.Assert(m["abc"], NotNil)
803 c.Assert(m["def"], IsNil)
804 c.Assert(m["ghi"], NotNil)
805 c.Assert(m["jkl"], IsNil)
806
807 c.Assert(m["abc"].received, Equals, "1")
808 c.Assert(m["ghi"].received, Equals, "3")
809}
810
811func (s *S) TestUnmarshalSetterErrors(c *C) {
812 boom := errors.New("BOOM")
813 setterResult["2"] = boom
814 defer delete(setterResult, "2")
815
816 m := map[string]*setterType{}
817 data := wrapInDoc("\x02abc\x00\x02\x00\x00\x001\x00" +
818 "\x02def\x00\x02\x00\x00\x002\x00" +
819 "\x02ghi\x00\x02\x00\x00\x003\x00")
820 err := bson.Unmarshal([]byte(data), m)
821 c.Assert(err, Equals, boom)
822 c.Assert(m["abc"], NotNil)
823 c.Assert(m["def"], IsNil)
824 c.Assert(m["ghi"], IsNil)
825
826 c.Assert(m["abc"].received, Equals, "1")
827}
828
829func (s *S) TestDMap(c *C) {
830 d := bson.D{{"a", 1}, {"b", 2}}
831 c.Assert(d.Map(), DeepEquals, bson.M{"a": 1, "b": 2})
832}
833
834func (s *S) TestUnmarshalSetterSetZero(c *C) {
835 setterResult["foo"] = bson.SetZero
836 defer delete(setterResult, "field")
837
838 data, err := bson.Marshal(bson.M{"field": "foo"})
839 c.Assert(err, IsNil)
840
841 m := map[string]*setterType{}
842 err = bson.Unmarshal([]byte(data), m)
843 c.Assert(err, IsNil)
844
845 value, ok := m["field"]
846 c.Assert(ok, Equals, true)
847 c.Assert(value, IsNil)
848}
849
850// --------------------------------------------------------------------------
851// Getter test cases.
852
853type typeWithGetter struct {
854 result interface{}
855 err error
856}
857
858func (t *typeWithGetter) GetBSON() (interface{}, error) {
859 if t == nil {
860 return "<value is nil>", nil
861 }
862 return t.result, t.err
863}
864
865type docWithGetterField struct {
866 Field *typeWithGetter "_"
867}
868
869func (s *S) TestMarshalAllItemsWithGetter(c *C) {
870 for i, item := range allItems {
871 if item.data == "" {
872 continue
873 }
874 obj := &docWithGetterField{}
875 obj.Field = &typeWithGetter{result: item.obj.(bson.M)["_"]}
876 data, err := bson.Marshal(obj)
877 c.Assert(err, IsNil)
878 c.Assert(string(data), Equals, wrapInDoc(item.data),
879 Commentf("Failed on item #%d", i))
880 }
881}
882
883func (s *S) TestMarshalWholeDocumentWithGetter(c *C) {
884 obj := &typeWithGetter{result: sampleItems[0].obj}
885 data, err := bson.Marshal(obj)
886 c.Assert(err, IsNil)
887 c.Assert(string(data), Equals, sampleItems[0].data)
888}
889
890func (s *S) TestGetterErrors(c *C) {
891 e := errors.New("oops")
892
893 obj1 := &docWithGetterField{}
894 obj1.Field = &typeWithGetter{sampleItems[0].obj, e}
895 data, err := bson.Marshal(obj1)
896 c.Assert(err, ErrorMatches, "oops")
897 c.Assert(data, IsNil)
898
899 obj2 := &typeWithGetter{sampleItems[0].obj, e}
900 data, err = bson.Marshal(obj2)
901 c.Assert(err, ErrorMatches, "oops")
902 c.Assert(data, IsNil)
903}
904
905type intGetter int64
906
907func (t intGetter) GetBSON() (interface{}, error) {
908 return int64(t), nil
909}
910
911type typeWithIntGetter struct {
912 V intGetter ",minsize"
913}
914
915func (s *S) TestMarshalShortWithGetter(c *C) {
916 obj := typeWithIntGetter{42}
917 data, err := bson.Marshal(obj)
918 c.Assert(err, IsNil)
919 m := bson.M{}
920 err = bson.Unmarshal(data, m)
921 c.Assert(err, IsNil)
922 c.Assert(m["v"], Equals, 42)
923}
924
925func (s *S) TestMarshalWithGetterNil(c *C) {
926 obj := docWithGetterField{}
927 data, err := bson.Marshal(obj)
928 c.Assert(err, IsNil)
929 m := bson.M{}
930 err = bson.Unmarshal(data, m)
931 c.Assert(err, IsNil)
932 c.Assert(m, DeepEquals, bson.M{"_": "<value is nil>"})
933}
934
935// --------------------------------------------------------------------------
936// Cross-type conversion tests.
937
938type crossTypeItem struct {
939 obj1 interface{}
940 obj2 interface{}
941}
942
943type condStr struct {
944 V string ",omitempty"
945}
946type condStrNS struct {
947 V string `a:"A" bson:",omitempty" b:"B"`
948}
949type condBool struct {
950 V bool ",omitempty"
951}
952type condInt struct {
953 V int ",omitempty"
954}
955type condUInt struct {
956 V uint ",omitempty"
957}
958type condFloat struct {
959 V float64 ",omitempty"
960}
961type condIface struct {
962 V interface{} ",omitempty"
963}
964type condPtr struct {
965 V *bool ",omitempty"
966}
967type condSlice struct {
968 V []string ",omitempty"
969}
970type condMap struct {
971 V map[string]int ",omitempty"
972}
973type namedCondStr struct {
974 V string "myv,omitempty"
975}
976type condTime struct {
977 V time.Time ",omitempty"
978}
979type condStruct struct {
980 V struct{ A []int } ",omitempty"
981}
982
983type shortInt struct {
984 V int64 ",minsize"
985}
986type shortUint struct {
987 V uint64 ",minsize"
988}
989type shortIface struct {
990 V interface{} ",minsize"
991}
992type shortPtr struct {
993 V *int64 ",minsize"
994}
995type shortNonEmptyInt struct {
996 V int64 ",minsize,omitempty"
997}
998
999type inlineInt struct {
1000 V struct{ A, B int } ",inline"
1001}
1002type inlineCantPtr struct {
1003 V *struct{ A, B int } ",inline"
1004}
1005type inlineDupName struct {
1006 A int
1007 V struct{ A, B int } ",inline"
1008}
1009type inlineMap struct {
1010 A int
1011 M map[string]interface{} ",inline"
1012}
1013type inlineMapInt struct {
1014 A int
1015 M map[string]int ",inline"
1016}
1017type inlineMapMyM struct {
1018 A int
1019 M MyM ",inline"
1020}
1021type inlineDupMap struct {
1022 M1 map[string]interface{} ",inline"
1023 M2 map[string]interface{} ",inline"
1024}
1025type inlineBadKeyMap struct {
1026 M map[int]int ",inline"
1027}
1028
1029type getterSetterD bson.D
1030
1031func (s getterSetterD) GetBSON() (interface{}, error) {
1032 if len(s) == 0 {
1033 return bson.D{}, nil
1034 }
1035 return bson.D(s[:len(s)-1]), nil
1036}
1037
1038func (s *getterSetterD) SetBSON(raw bson.Raw) error {
1039 var doc bson.D
1040 err := raw.Unmarshal(&doc)
1041 doc = append(doc, bson.DocElem{"suffix", true})
1042 *s = getterSetterD(doc)
1043 return err
1044}
1045
1046type getterSetterInt int
1047
1048func (i getterSetterInt) GetBSON() (interface{}, error) {
1049 return bson.D{{"a", int(i)}}, nil
1050}
1051
1052func (i *getterSetterInt) SetBSON(raw bson.Raw) error {
1053 var doc struct{ A int }
1054 err := raw.Unmarshal(&doc)
1055 *i = getterSetterInt(doc.A)
1056 return err
1057}
1058
1059type ifaceType interface {
1060 Hello()
1061}
1062
1063type ifaceSlice []ifaceType
1064
1065func (s *ifaceSlice) SetBSON(raw bson.Raw) error {
1066 var ns []int
1067 if err := raw.Unmarshal(&ns); err != nil {
1068 return err
1069 }
1070 *s = make(ifaceSlice, ns[0])
1071 return nil
1072}
1073
1074func (s ifaceSlice) GetBSON() (interface{}, error) {
1075 return []int{len(s)}, nil
1076}
1077
1078type (
1079 MyString string
1080 MyBytes []byte
1081 MyBool bool
1082 MyD []bson.DocElem
1083 MyRawD []bson.RawDocElem
1084 MyM map[string]interface{}
1085)
1086
1087var (
1088 truevar = true
1089 falsevar = false
1090
1091 int64var = int64(42)
1092 int64ptr = &int64var
1093 intvar = int(42)
1094 intptr = &intvar
1095
1096 gsintvar = getterSetterInt(42)
1097)
1098
1099func parseURL(s string) *url.URL {
1100 u, err := url.Parse(s)
1101 if err != nil {
1102 panic(err)
1103 }
1104 return u
1105}
1106
1107// That's a pretty fun test. It will dump the first item, generate a zero
1108// value equivalent to the second one, load the dumped data onto it, and then
1109// verify that the resulting value is deep-equal to the untouched second value.
1110// Then, it will do the same in the *opposite* direction!
1111var twoWayCrossItems = []crossTypeItem{
1112 // int<=>int
1113 {&struct{ I int }{42}, &struct{ I int8 }{42}},
1114 {&struct{ I int }{42}, &struct{ I int32 }{42}},
1115 {&struct{ I int }{42}, &struct{ I int64 }{42}},
1116 {&struct{ I int8 }{42}, &struct{ I int32 }{42}},
1117 {&struct{ I int8 }{42}, &struct{ I int64 }{42}},
1118 {&struct{ I int32 }{42}, &struct{ I int64 }{42}},
1119
1120 // uint<=>uint
1121 {&struct{ I uint }{42}, &struct{ I uint8 }{42}},
1122 {&struct{ I uint }{42}, &struct{ I uint32 }{42}},
1123 {&struct{ I uint }{42}, &struct{ I uint64 }{42}},
1124 {&struct{ I uint8 }{42}, &struct{ I uint32 }{42}},
1125 {&struct{ I uint8 }{42}, &struct{ I uint64 }{42}},
1126 {&struct{ I uint32 }{42}, &struct{ I uint64 }{42}},
1127
1128 // float32<=>float64
1129 {&struct{ I float32 }{42}, &struct{ I float64 }{42}},
1130
1131 // int<=>uint
1132 {&struct{ I uint }{42}, &struct{ I int }{42}},
1133 {&struct{ I uint }{42}, &struct{ I int8 }{42}},
1134 {&struct{ I uint }{42}, &struct{ I int32 }{42}},
1135 {&struct{ I uint }{42}, &struct{ I int64 }{42}},
1136 {&struct{ I uint8 }{42}, &struct{ I int }{42}},
1137 {&struct{ I uint8 }{42}, &struct{ I int8 }{42}},
1138 {&struct{ I uint8 }{42}, &struct{ I int32 }{42}},
1139 {&struct{ I uint8 }{42}, &struct{ I int64 }{42}},
1140 {&struct{ I uint32 }{42}, &struct{ I int }{42}},
1141 {&struct{ I uint32 }{42}, &struct{ I int8 }{42}},
1142 {&struct{ I uint32 }{42}, &struct{ I int32 }{42}},
1143 {&struct{ I uint32 }{42}, &struct{ I int64 }{42}},
1144 {&struct{ I uint64 }{42}, &struct{ I int }{42}},
1145 {&struct{ I uint64 }{42}, &struct{ I int8 }{42}},
1146 {&struct{ I uint64 }{42}, &struct{ I int32 }{42}},
1147 {&struct{ I uint64 }{42}, &struct{ I int64 }{42}},
1148
1149 // int <=> float
1150 {&struct{ I int }{42}, &struct{ I float64 }{42}},
1151
1152 // int <=> bool
1153 {&struct{ I int }{1}, &struct{ I bool }{true}},
1154 {&struct{ I int }{0}, &struct{ I bool }{false}},
1155
1156 // uint <=> float64
1157 {&struct{ I uint }{42}, &struct{ I float64 }{42}},
1158
1159 // uint <=> bool
1160 {&struct{ I uint }{1}, &struct{ I bool }{true}},
1161 {&struct{ I uint }{0}, &struct{ I bool }{false}},
1162
1163 // float64 <=> bool
1164 {&struct{ I float64 }{1}, &struct{ I bool }{true}},
1165 {&struct{ I float64 }{0}, &struct{ I bool }{false}},
1166
1167 // string <=> string and string <=> []byte
1168 {&struct{ S []byte }{[]byte("abc")}, &struct{ S string }{"abc"}},
1169 {&struct{ S []byte }{[]byte("def")}, &struct{ S bson.Symbol }{"def"}},
1170 {&struct{ S string }{"ghi"}, &struct{ S bson.Symbol }{"ghi"}},
1171
1172 // map <=> struct
1173 {&struct {
1174 A struct {
1175 B, C int
1176 }
1177 }{struct{ B, C int }{1, 2}},
1178 map[string]map[string]int{"a": map[string]int{"b": 1, "c": 2}}},
1179
1180 {&struct{ A bson.Symbol }{"abc"}, map[string]string{"a": "abc"}},
1181 {&struct{ A bson.Symbol }{"abc"}, map[string][]byte{"a": []byte("abc")}},
1182 {&struct{ A []byte }{[]byte("abc")}, map[string]string{"a": "abc"}},
1183 {&struct{ A uint }{42}, map[string]int{"a": 42}},
1184 {&struct{ A uint }{42}, map[string]float64{"a": 42}},
1185 {&struct{ A uint }{1}, map[string]bool{"a": true}},
1186 {&struct{ A int }{42}, map[string]uint{"a": 42}},
1187 {&struct{ A int }{42}, map[string]float64{"a": 42}},
1188 {&struct{ A int }{1}, map[string]bool{"a": true}},
1189 {&struct{ A float64 }{42}, map[string]float32{"a": 42}},
1190 {&struct{ A float64 }{42}, map[string]int{"a": 42}},
1191 {&struct{ A float64 }{42}, map[string]uint{"a": 42}},
1192 {&struct{ A float64 }{1}, map[string]bool{"a": true}},
1193 {&struct{ A bool }{true}, map[string]int{"a": 1}},
1194 {&struct{ A bool }{true}, map[string]uint{"a": 1}},
1195 {&struct{ A bool }{true}, map[string]float64{"a": 1}},
1196 {&struct{ A **byte }{&byteptr}, map[string]byte{"a": 8}},
1197
1198 // url.URL <=> string
1199 {&struct{ URL *url.URL }{parseURL("h://e.c/p")}, map[string]string{"url": "h://e.c/p"}},
1200 {&struct{ URL url.URL }{*parseURL("h://e.c/p")}, map[string]string{"url": "h://e.c/p"}},
1201
1202 // Slices
1203 {&struct{ S []int }{[]int{1, 2, 3}}, map[string][]int{"s": []int{1, 2, 3}}},
1204 {&struct{ S *[]int }{&[]int{1, 2, 3}}, map[string][]int{"s": []int{1, 2, 3}}},
1205
1206 // Conditionals
1207 {&condBool{true}, map[string]bool{"v": true}},
1208 {&condBool{}, map[string]bool{}},
1209 {&condInt{1}, map[string]int{"v": 1}},
1210 {&condInt{}, map[string]int{}},
1211 {&condUInt{1}, map[string]uint{"v": 1}},
1212 {&condUInt{}, map[string]uint{}},
1213 {&condFloat{}, map[string]int{}},
1214 {&condStr{"yo"}, map[string]string{"v": "yo"}},
1215 {&condStr{}, map[string]string{}},
1216 {&condStrNS{"yo"}, map[string]string{"v": "yo"}},
1217 {&condStrNS{}, map[string]string{}},
1218 {&condSlice{[]string{"yo"}}, map[string][]string{"v": []string{"yo"}}},
1219 {&condSlice{}, map[string][]string{}},
1220 {&condMap{map[string]int{"k": 1}}, bson.M{"v": bson.M{"k": 1}}},
1221 {&condMap{}, map[string][]string{}},
1222 {&condIface{"yo"}, map[string]string{"v": "yo"}},
1223 {&condIface{""}, map[string]string{"v": ""}},
1224 {&condIface{}, map[string]string{}},
1225 {&condPtr{&truevar}, map[string]bool{"v": true}},
1226 {&condPtr{&falsevar}, map[string]bool{"v": false}},
1227 {&condPtr{}, map[string]string{}},
1228
1229 {&condTime{time.Unix(123456789, 123e6)}, map[string]time.Time{"v": time.Unix(123456789, 123e6)}},
1230 {&condTime{}, map[string]string{}},
1231
1232 {&condStruct{struct{ A []int }{[]int{1}}}, bson.M{"v": bson.M{"a": []interface{}{1}}}},
1233 {&condStruct{struct{ A []int }{}}, bson.M{}},
1234
1235 {&namedCondStr{"yo"}, map[string]string{"myv": "yo"}},
1236 {&namedCondStr{}, map[string]string{}},
1237
1238 {&shortInt{1}, map[string]interface{}{"v": 1}},
1239 {&shortInt{1 << 30}, map[string]interface{}{"v": 1 << 30}},
1240 {&shortInt{1 << 31}, map[string]interface{}{"v": int64(1 << 31)}},
1241 {&shortUint{1 << 30}, map[string]interface{}{"v": 1 << 30}},
1242 {&shortUint{1 << 31}, map[string]interface{}{"v": int64(1 << 31)}},
1243 {&shortIface{int64(1) << 31}, map[string]interface{}{"v": int64(1 << 31)}},
1244 {&shortPtr{int64ptr}, map[string]interface{}{"v": intvar}},
1245
1246 {&shortNonEmptyInt{1}, map[string]interface{}{"v": 1}},
1247 {&shortNonEmptyInt{1 << 31}, map[string]interface{}{"v": int64(1 << 31)}},
1248 {&shortNonEmptyInt{}, map[string]interface{}{}},
1249
1250 {&inlineInt{struct{ A, B int }{1, 2}}, map[string]interface{}{"a": 1, "b": 2}},
1251 {&inlineMap{A: 1, M: map[string]interface{}{"b": 2}}, map[string]interface{}{"a": 1, "b": 2}},
1252 {&inlineMap{A: 1, M: nil}, map[string]interface{}{"a": 1}},
1253 {&inlineMapInt{A: 1, M: map[string]int{"b": 2}}, map[string]int{"a": 1, "b": 2}},
1254 {&inlineMapInt{A: 1, M: nil}, map[string]int{"a": 1}},
1255 {&inlineMapMyM{A: 1, M: MyM{"b": MyM{"c": 3}}}, map[string]interface{}{"a": 1, "b": map[string]interface{}{"c": 3}}},
1256
1257 // []byte <=> MyBytes
1258 {&struct{ B MyBytes }{[]byte("abc")}, map[string]string{"b": "abc"}},
1259 {&struct{ B MyBytes }{[]byte{}}, map[string]string{"b": ""}},
1260 {&struct{ B MyBytes }{}, map[string]bool{}},
1261 {&struct{ B []byte }{[]byte("abc")}, map[string]MyBytes{"b": []byte("abc")}},
1262
1263 // bool <=> MyBool
1264 {&struct{ B MyBool }{true}, map[string]bool{"b": true}},
1265 {&struct{ B MyBool }{}, map[string]bool{"b": false}},
1266 {&struct{ B MyBool }{}, map[string]string{}},
1267 {&struct{ B bool }{}, map[string]MyBool{"b": false}},
1268
1269 // arrays
1270 {&struct{ V [2]int }{[...]int{1, 2}}, map[string][2]int{"v": [2]int{1, 2}}},
1271 {&struct{ V [2]byte }{[...]byte{1, 2}}, map[string][2]byte{"v": [2]byte{1, 2}}},
1272
1273 // zero time
1274 {&struct{ V time.Time }{}, map[string]interface{}{"v": time.Time{}}},
1275
1276 // zero time + 1 second + 1 millisecond; overflows int64 as nanoseconds
1277 {&struct{ V time.Time }{time.Unix(-62135596799, 1e6).Local()},
1278 map[string]interface{}{"v": time.Unix(-62135596799, 1e6).Local()}},
1279
1280 // bson.D <=> []DocElem
1281 {&bson.D{{"a", bson.D{{"b", 1}, {"c", 2}}}}, &bson.D{{"a", bson.D{{"b", 1}, {"c", 2}}}}},
1282 {&bson.D{{"a", bson.D{{"b", 1}, {"c", 2}}}}, &MyD{{"a", MyD{{"b", 1}, {"c", 2}}}}},
1283 {&struct{ V MyD }{MyD{{"a", 1}}}, &bson.D{{"v", bson.D{{"a", 1}}}}},
1284
1285 // bson.RawD <=> []RawDocElem
1286 {&bson.RawD{{"a", bson.Raw{0x08, []byte{0x01}}}}, &bson.RawD{{"a", bson.Raw{0x08, []byte{0x01}}}}},
1287 {&bson.RawD{{"a", bson.Raw{0x08, []byte{0x01}}}}, &MyRawD{{"a", bson.Raw{0x08, []byte{0x01}}}}},
1288
1289 // bson.M <=> map
1290 {bson.M{"a": bson.M{"b": 1, "c": 2}}, MyM{"a": MyM{"b": 1, "c": 2}}},
1291 {bson.M{"a": bson.M{"b": 1, "c": 2}}, map[string]interface{}{"a": map[string]interface{}{"b": 1, "c": 2}}},
1292
1293 // bson.M <=> map[MyString]
1294 {bson.M{"a": bson.M{"b": 1, "c": 2}}, map[MyString]interface{}{"a": map[MyString]interface{}{"b": 1, "c": 2}}},
1295
1296 // json.Number <=> int64, float64
1297 {&struct{ N json.Number }{"5"}, map[string]interface{}{"n": int64(5)}},
1298 {&struct{ N json.Number }{"5.05"}, map[string]interface{}{"n": 5.05}},
1299 {&struct{ N json.Number }{"9223372036854776000"}, map[string]interface{}{"n": float64(1 << 63)}},
1300
1301 // bson.D <=> non-struct getter/setter
1302 {&bson.D{{"a", 1}}, &getterSetterD{{"a", 1}, {"suffix", true}}},
1303 {&bson.D{{"a", 42}}, &gsintvar},
1304
1305 // Interface slice setter.
1306 {&struct{ V ifaceSlice }{ifaceSlice{nil, nil, nil}}, bson.M{"v": []interface{}{3}}},
1307}
1308
1309// Same thing, but only one way (obj1 => obj2).
1310var oneWayCrossItems = []crossTypeItem{
1311 // map <=> struct
1312 {map[string]interface{}{"a": 1, "b": "2", "c": 3}, map[string]int{"a": 1, "c": 3}},
1313
1314 // inline map elides badly typed values
1315 {map[string]interface{}{"a": 1, "b": "2", "c": 3}, &inlineMapInt{A: 1, M: map[string]int{"c": 3}}},
1316
1317 // Can't decode int into struct.
1318 {bson.M{"a": bson.M{"b": 2}}, &struct{ A bool }{}},
1319
1320 // Would get decoded into a int32 too in the opposite direction.
1321 {&shortIface{int64(1) << 30}, map[string]interface{}{"v": 1 << 30}},
1322
1323 // Ensure omitempty on struct with private fields works properly.
1324 {&struct {
1325 V struct{ v time.Time } ",omitempty"
1326 }{}, map[string]interface{}{}},
1327}
1328
1329func testCrossPair(c *C, dump interface{}, load interface{}) {
1330 c.Logf("Dump: %#v", dump)
1331 c.Logf("Load: %#v", load)
1332 zero := makeZeroDoc(load)
1333 data, err := bson.Marshal(dump)
1334 c.Assert(err, IsNil)
1335 c.Logf("Dumped: %#v", string(data))
1336 err = bson.Unmarshal(data, zero)
1337 c.Assert(err, IsNil)
1338 c.Logf("Loaded: %#v", zero)
1339 c.Assert(zero, DeepEquals, load)
1340}
1341
1342func (s *S) TestTwoWayCrossPairs(c *C) {
1343 for _, item := range twoWayCrossItems {
1344 testCrossPair(c, item.obj1, item.obj2)
1345 testCrossPair(c, item.obj2, item.obj1)
1346 }
1347}
1348
1349func (s *S) TestOneWayCrossPairs(c *C) {
1350 for _, item := range oneWayCrossItems {
1351 testCrossPair(c, item.obj1, item.obj2)
1352 }
1353}
1354
1355// --------------------------------------------------------------------------
1356// ObjectId hex representation test.
1357
1358func (s *S) TestObjectIdHex(c *C) {
1359 id := bson.ObjectIdHex("4d88e15b60f486e428412dc9")
1360 c.Assert(id.String(), Equals, `ObjectIdHex("4d88e15b60f486e428412dc9")`)
1361 c.Assert(id.Hex(), Equals, "4d88e15b60f486e428412dc9")
1362}
1363
1364func (s *S) TestIsObjectIdHex(c *C) {
1365 test := []struct {
1366 id string
1367 valid bool
1368 }{
1369 {"4d88e15b60f486e428412dc9", true},
1370 {"4d88e15b60f486e428412dc", false},
1371 {"4d88e15b60f486e428412dc9e", false},
1372 {"4d88e15b60f486e428412dcx", false},
1373 }
1374 for _, t := range test {
1375 c.Assert(bson.IsObjectIdHex(t.id), Equals, t.valid)
1376 }
1377}
1378
1379// --------------------------------------------------------------------------
1380// ObjectId parts extraction tests.
1381
1382type objectIdParts struct {
1383 id bson.ObjectId
1384 timestamp int64
1385 machine []byte
1386 pid uint16
1387 counter int32
1388}
1389
1390var objectIds = []objectIdParts{
1391 objectIdParts{
1392 bson.ObjectIdHex("4d88e15b60f486e428412dc9"),
1393 1300816219,
1394 []byte{0x60, 0xf4, 0x86},
1395 0xe428,
1396 4271561,
1397 },
1398 objectIdParts{
1399 bson.ObjectIdHex("000000000000000000000000"),
1400 0,
1401 []byte{0x00, 0x00, 0x00},
1402 0x0000,
1403 0,
1404 },
1405 objectIdParts{
1406 bson.ObjectIdHex("00000000aabbccddee000001"),
1407 0,
1408 []byte{0xaa, 0xbb, 0xcc},
1409 0xddee,
1410 1,
1411 },
1412}
1413
1414func (s *S) TestObjectIdPartsExtraction(c *C) {
1415 for i, v := range objectIds {
1416 t := time.Unix(v.timestamp, 0)
1417 c.Assert(v.id.Time(), Equals, t, Commentf("#%d Wrong timestamp value", i))
1418 c.Assert(v.id.Machine(), DeepEquals, v.machine, Commentf("#%d Wrong machine id value", i))
1419 c.Assert(v.id.Pid(), Equals, v.pid, Commentf("#%d Wrong pid value", i))
1420 c.Assert(v.id.Counter(), Equals, v.counter, Commentf("#%d Wrong counter value", i))
1421 }
1422}
1423
1424func (s *S) TestNow(c *C) {
1425 before := time.Now()
1426 time.Sleep(1e6)
1427 now := bson.Now()
1428 time.Sleep(1e6)
1429 after := time.Now()
1430 c.Assert(now.After(before) && now.Before(after), Equals, true, Commentf("now=%s, before=%s, after=%s", now, before, after))
1431}
1432
1433// --------------------------------------------------------------------------
1434// ObjectId generation tests.
1435
1436func (s *S) TestNewObjectId(c *C) {
1437 // Generate 10 ids
1438 ids := make([]bson.ObjectId, 10)
1439 for i := 0; i < 10; i++ {
1440 ids[i] = bson.NewObjectId()
1441 }
1442 for i := 1; i < 10; i++ {
1443 prevId := ids[i-1]
1444 id := ids[i]
1445 // Test for uniqueness among all other 9 generated ids
1446 for j, tid := range ids {
1447 if j != i {
1448 c.Assert(id, Not(Equals), tid, Commentf("Generated ObjectId is not unique"))
1449 }
1450 }
1451 // Check that timestamp was incremented and is within 30 seconds of the previous one
1452 secs := id.Time().Sub(prevId.Time()).Seconds()
1453 c.Assert((secs >= 0 && secs <= 30), Equals, true, Commentf("Wrong timestamp in generated ObjectId"))
1454 // Check that machine ids are the same
1455 c.Assert(id.Machine(), DeepEquals, prevId.Machine())
1456 // Check that pids are the same
1457 c.Assert(id.Pid(), Equals, prevId.Pid())
1458 // Test for proper increment
1459 delta := int(id.Counter() - prevId.Counter())
1460 c.Assert(delta, Equals, 1, Commentf("Wrong increment in generated ObjectId"))
1461 }
1462}
1463
1464func (s *S) TestNewObjectIdWithTime(c *C) {
1465 t := time.Unix(12345678, 0)
1466 id := bson.NewObjectIdWithTime(t)
1467 c.Assert(id.Time(), Equals, t)
1468 c.Assert(id.Machine(), DeepEquals, []byte{0x00, 0x00, 0x00})
1469 c.Assert(int(id.Pid()), Equals, 0)
1470 c.Assert(int(id.Counter()), Equals, 0)
1471}
1472
1473// --------------------------------------------------------------------------
1474// ObjectId JSON marshalling.
1475
1476type jsonType struct {
1477 Id bson.ObjectId
1478}
1479
1480var jsonIdTests = []struct {
1481 value jsonType
1482 json string
1483 marshal bool
1484 unmarshal bool
1485 error string
1486}{{
1487 value: jsonType{Id: bson.ObjectIdHex("4d88e15b60f486e428412dc9")},
1488 json: `{"Id":"4d88e15b60f486e428412dc9"}`,
1489 marshal: true,
1490 unmarshal: true,
1491}, {
1492 value: jsonType{},
1493 json: `{"Id":""}`,
1494 marshal: true,
1495 unmarshal: true,
1496}, {
1497 value: jsonType{},
1498 json: `{"Id":null}`,
1499 marshal: false,
1500 unmarshal: true,
1501}, {
1502 json: `{"Id":"4d88e15b60f486e428412dc9A"}`,
1503 error: `Invalid ObjectId in JSON: "4d88e15b60f486e428412dc9A"`,
1504 marshal: false,
1505 unmarshal: true,
1506}, {
1507 json: `{"Id":"4d88e15b60f486e428412dcZ"}`,
1508 error: `Invalid ObjectId in JSON: "4d88e15b60f486e428412dcZ" .*`,
1509 marshal: false,
1510 unmarshal: true,
1511}}
1512
1513func (s *S) TestObjectIdJSONMarshaling(c *C) {
1514 for _, test := range jsonIdTests {
1515 if test.marshal {
1516 data, err := json.Marshal(&test.value)
1517 if test.error == "" {
1518 c.Assert(err, IsNil)
1519 c.Assert(string(data), Equals, test.json)
1520 } else {
1521 c.Assert(err, ErrorMatches, test.error)
1522 }
1523 }
1524
1525 if test.unmarshal {
1526 var value jsonType
1527 err := json.Unmarshal([]byte(test.json), &value)
1528 if test.error == "" {
1529 c.Assert(err, IsNil)
1530 c.Assert(value, DeepEquals, test.value)
1531 } else {
1532 c.Assert(err, ErrorMatches, test.error)
1533 }
1534 }
1535 }
1536}
1537
1538// --------------------------------------------------------------------------
1539// Some simple benchmarks.
1540
1541type BenchT struct {
1542 A, B, C, D, E, F string
1543}
1544
1545type BenchRawT struct {
1546 A string
1547 B int
1548 C bson.M
1549 D []float64
1550}
1551
1552func (s *S) BenchmarkUnmarhsalStruct(c *C) {
1553 v := BenchT{A: "A", D: "D", E: "E"}
1554 data, err := bson.Marshal(&v)
1555 if err != nil {
1556 panic(err)
1557 }
1558 c.ResetTimer()
1559 for i := 0; i < c.N; i++ {
1560 err = bson.Unmarshal(data, &v)
1561 }
1562 if err != nil {
1563 panic(err)
1564 }
1565}
1566
1567func (s *S) BenchmarkUnmarhsalMap(c *C) {
1568 m := bson.M{"a": "a", "d": "d", "e": "e"}
1569 data, err := bson.Marshal(&m)
1570 if err != nil {
1571 panic(err)
1572 }
1573 c.ResetTimer()
1574 for i := 0; i < c.N; i++ {
1575 err = bson.Unmarshal(data, &m)
1576 }
1577 if err != nil {
1578 panic(err)
1579 }
1580}
1581
1582func (s *S) BenchmarkUnmarshalRaw(c *C) {
1583 var err error
1584 m := BenchRawT{
1585 A: "test_string",
1586 B: 123,
1587 C: bson.M{
1588 "subdoc_int": 12312,
1589 "subdoc_doc": bson.M{"1": 1},
1590 },
1591 D: []float64{0.0, 1.3333, -99.9997, 3.1415},
1592 }
1593 data, err := bson.Marshal(&m)
1594 if err != nil {
1595 panic(err)
1596 }
1597 raw := bson.Raw{}
1598 c.ResetTimer()
1599 for i := 0; i < c.N; i++ {
1600 err = bson.Unmarshal(data, &raw)
1601 }
1602 if err != nil {
1603 panic(err)
1604 }
1605}
vendor/gopkg.in/mgo.v2/bson/decode.go+825−0
@@ -0,0 +1,825 @@
1// BSON library for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26// gobson - BSON library for Go.
27
28package bson
29
30import (
31 "fmt"
32 "math"
33 "net/url"
34 "reflect"
35 "strconv"
36 "sync"
37 "time"
38)
39
40type decoder struct {
41 in []byte
42 i int
43 docType reflect.Type
44}
45
46var typeM = reflect.TypeOf(M{})
47
48func newDecoder(in []byte) *decoder {
49 return &decoder{in, 0, typeM}
50}
51
52// --------------------------------------------------------------------------
53// Some helper functions.
54
55func corrupted() {
56 panic("Document is corrupted")
57}
58
59func settableValueOf(i interface{}) reflect.Value {
60 v := reflect.ValueOf(i)
61 sv := reflect.New(v.Type()).Elem()
62 sv.Set(v)
63 return sv
64}
65
66// --------------------------------------------------------------------------
67// Unmarshaling of documents.
68
69const (
70 setterUnknown = iota
71 setterNone
72 setterType
73 setterAddr
74)
75
76var setterStyles map[reflect.Type]int
77var setterIface reflect.Type
78var setterMutex sync.RWMutex
79
80func init() {
81 var iface Setter
82 setterIface = reflect.TypeOf(&iface).Elem()
83 setterStyles = make(map[reflect.Type]int)
84}
85
86func setterStyle(outt reflect.Type) int {
87 setterMutex.RLock()
88 style := setterStyles[outt]
89 setterMutex.RUnlock()
90 if style == setterUnknown {
91 setterMutex.Lock()
92 defer setterMutex.Unlock()
93 if outt.Implements(setterIface) {
94 setterStyles[outt] = setterType
95 } else if reflect.PtrTo(outt).Implements(setterIface) {
96 setterStyles[outt] = setterAddr
97 } else {
98 setterStyles[outt] = setterNone
99 }
100 style = setterStyles[outt]
101 }
102 return style
103}
104
105func getSetter(outt reflect.Type, out reflect.Value) Setter {
106 style := setterStyle(outt)
107 if style == setterNone {
108 return nil
109 }
110 if style == setterAddr {
111 if !out.CanAddr() {
112 return nil
113 }
114 out = out.Addr()
115 } else if outt.Kind() == reflect.Ptr && out.IsNil() {
116 out.Set(reflect.New(outt.Elem()))
117 }
118 return out.Interface().(Setter)
119}
120
121func clearMap(m reflect.Value) {
122 var none reflect.Value
123 for _, k := range m.MapKeys() {
124 m.SetMapIndex(k, none)
125 }
126}
127
128func (d *decoder) readDocTo(out reflect.Value) {
129 var elemType reflect.Type
130 outt := out.Type()
131 outk := outt.Kind()
132
133 for {
134 if outk == reflect.Ptr && out.IsNil() {
135 out.Set(reflect.New(outt.Elem()))
136 }
137 if setter := getSetter(outt, out); setter != nil {
138 var raw Raw
139 d.readDocTo(reflect.ValueOf(&raw))
140 err := setter.SetBSON(raw)
141 if _, ok := err.(*TypeError); err != nil && !ok {
142 panic(err)
143 }
144 return
145 }
146 if outk == reflect.Ptr {
147 out = out.Elem()
148 outt = out.Type()
149 outk = out.Kind()
150 continue
151 }
152 break
153 }
154
155 var fieldsMap map[string]fieldInfo
156 var inlineMap reflect.Value
157 start := d.i
158
159 origout := out
160 if outk == reflect.Interface {
161 if d.docType.Kind() == reflect.Map {
162 mv := reflect.MakeMap(d.docType)
163 out.Set(mv)
164 out = mv
165 } else {
166 dv := reflect.New(d.docType).Elem()
167 out.Set(dv)
168 out = dv
169 }
170 outt = out.Type()
171 outk = outt.Kind()
172 }
173
174 docType := d.docType
175 keyType := typeString
176 convertKey := false
177 switch outk {
178 case reflect.Map:
179 keyType = outt.Key()
180 if keyType.Kind() != reflect.String {
181 panic("BSON map must have string keys. Got: " + outt.String())
182 }
183 if keyType != typeString {
184 convertKey = true
185 }
186 elemType = outt.Elem()
187 if elemType == typeIface {
188 d.docType = outt
189 }
190 if out.IsNil() {
191 out.Set(reflect.MakeMap(out.Type()))
192 } else if out.Len() > 0 {
193 clearMap(out)
194 }
195 case reflect.Struct:
196 if outt != typeRaw {
197 sinfo, err := getStructInfo(out.Type())
198 if err != nil {
199 panic(err)
200 }
201 fieldsMap = sinfo.FieldsMap
202 out.Set(sinfo.Zero)
203 if sinfo.InlineMap != -1 {
204 inlineMap = out.Field(sinfo.InlineMap)
205 if !inlineMap.IsNil() && inlineMap.Len() > 0 {
206 clearMap(inlineMap)
207 }
208 elemType = inlineMap.Type().Elem()
209 if elemType == typeIface {
210 d.docType = inlineMap.Type()
211 }
212 }
213 }
214 case reflect.Slice:
215 switch outt.Elem() {
216 case typeDocElem:
217 origout.Set(d.readDocElems(outt))
218 return
219 case typeRawDocElem:
220 origout.Set(d.readRawDocElems(outt))
221 return
222 }
223 fallthrough
224 default:
225 panic("Unsupported document type for unmarshalling: " + out.Type().String())
226 }
227
228 end := int(d.readInt32())
229 end += d.i - 4
230 if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' {
231 corrupted()
232 }
233 for d.in[d.i] != '\x00' {
234 kind := d.readByte()
235 name := d.readCStr()
236 if d.i >= end {
237 corrupted()
238 }
239
240 switch outk {
241 case reflect.Map:
242 e := reflect.New(elemType).Elem()
243 if d.readElemTo(e, kind) {
244 k := reflect.ValueOf(name)
245 if convertKey {
246 k = k.Convert(keyType)
247 }
248 out.SetMapIndex(k, e)
249 }
250 case reflect.Struct:
251 if outt == typeRaw {
252 d.dropElem(kind)
253 } else {
254 if info, ok := fieldsMap[name]; ok {
255 if info.Inline == nil {
256 d.readElemTo(out.Field(info.Num), kind)
257 } else {
258 d.readElemTo(out.FieldByIndex(info.Inline), kind)
259 }
260 } else if inlineMap.IsValid() {
261 if inlineMap.IsNil() {
262 inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
263 }
264 e := reflect.New(elemType).Elem()
265 if d.readElemTo(e, kind) {
266 inlineMap.SetMapIndex(reflect.ValueOf(name), e)
267 }
268 } else {
269 d.dropElem(kind)
270 }
271 }
272 case reflect.Slice:
273 }
274
275 if d.i >= end {
276 corrupted()
277 }
278 }
279 d.i++ // '\x00'
280 if d.i != end {
281 corrupted()
282 }
283 d.docType = docType
284
285 if outt == typeRaw {
286 out.Set(reflect.ValueOf(Raw{0x03, d.in[start:d.i]}))
287 }
288}
289
290func (d *decoder) readArrayDocTo(out reflect.Value) {
291 end := int(d.readInt32())
292 end += d.i - 4
293 if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' {
294 corrupted()
295 }
296 i := 0
297 l := out.Len()
298 for d.in[d.i] != '\x00' {
299 if i >= l {
300 panic("Length mismatch on array field")
301 }
302 kind := d.readByte()
303 for d.i < end && d.in[d.i] != '\x00' {
304 d.i++
305 }
306 if d.i >= end {
307 corrupted()
308 }
309 d.i++
310 d.readElemTo(out.Index(i), kind)
311 if d.i >= end {
312 corrupted()
313 }
314 i++
315 }
316 if i != l {
317 panic("Length mismatch on array field")
318 }
319 d.i++ // '\x00'
320 if d.i != end {
321 corrupted()
322 }
323}
324
325func (d *decoder) readSliceDoc(t reflect.Type) interface{} {
326 tmp := make([]reflect.Value, 0, 8)
327 elemType := t.Elem()
328
329 end := int(d.readInt32())
330 end += d.i - 4
331 if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' {
332 corrupted()
333 }
334 for d.in[d.i] != '\x00' {
335 kind := d.readByte()
336 for d.i < end && d.in[d.i] != '\x00' {
337 d.i++
338 }
339 if d.i >= end {
340 corrupted()
341 }
342 d.i++
343 e := reflect.New(elemType).Elem()
344 if d.readElemTo(e, kind) {
345 tmp = append(tmp, e)
346 }
347 if d.i >= end {
348 corrupted()
349 }
350 }
351 d.i++ // '\x00'
352 if d.i != end {
353 corrupted()
354 }
355
356 n := len(tmp)
357 slice := reflect.MakeSlice(t, n, n)
358 for i := 0; i != n; i++ {
359 slice.Index(i).Set(tmp[i])
360 }
361 return slice.Interface()
362}
363
364var typeSlice = reflect.TypeOf([]interface{}{})
365var typeIface = typeSlice.Elem()
366
367func (d *decoder) readDocElems(typ reflect.Type) reflect.Value {
368 docType := d.docType
369 d.docType = typ
370 slice := make([]DocElem, 0, 8)
371 d.readDocWith(func(kind byte, name string) {
372 e := DocElem{Name: name}
373 v := reflect.ValueOf(&e.Value)
374 if d.readElemTo(v.Elem(), kind) {
375 slice = append(slice, e)
376 }
377 })
378 slicev := reflect.New(typ).Elem()
379 slicev.Set(reflect.ValueOf(slice))
380 d.docType = docType
381 return slicev
382}
383
384func (d *decoder) readRawDocElems(typ reflect.Type) reflect.Value {
385 docType := d.docType
386 d.docType = typ
387 slice := make([]RawDocElem, 0, 8)
388 d.readDocWith(func(kind byte, name string) {
389 e := RawDocElem{Name: name}
390 v := reflect.ValueOf(&e.Value)
391 if d.readElemTo(v.Elem(), kind) {
392 slice = append(slice, e)
393 }
394 })
395 slicev := reflect.New(typ).Elem()
396 slicev.Set(reflect.ValueOf(slice))
397 d.docType = docType
398 return slicev
399}
400
401func (d *decoder) readDocWith(f func(kind byte, name string)) {
402 end := int(d.readInt32())
403 end += d.i - 4
404 if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' {
405 corrupted()
406 }
407 for d.in[d.i] != '\x00' {
408 kind := d.readByte()
409 name := d.readCStr()
410 if d.i >= end {
411 corrupted()
412 }
413 f(kind, name)
414 if d.i >= end {
415 corrupted()
416 }
417 }
418 d.i++ // '\x00'
419 if d.i != end {
420 corrupted()
421 }
422}
423
424// --------------------------------------------------------------------------
425// Unmarshaling of individual elements within a document.
426
427var blackHole = settableValueOf(struct{}{})
428
429func (d *decoder) dropElem(kind byte) {
430 d.readElemTo(blackHole, kind)
431}
432
433// Attempt to decode an element from the document and put it into out.
434// If the types are not compatible, the returned ok value will be
435// false and out will be unchanged.
436func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) {
437
438 start := d.i
439
440 if kind == '\x03' {
441 // Delegate unmarshaling of documents.
442 outt := out.Type()
443 outk := out.Kind()
444 switch outk {
445 case reflect.Interface, reflect.Ptr, reflect.Struct, reflect.Map:
446 d.readDocTo(out)
447 return true
448 }
449 if setterStyle(outt) != setterNone {
450 d.readDocTo(out)
451 return true
452 }
453 if outk == reflect.Slice {
454 switch outt.Elem() {
455 case typeDocElem:
456 out.Set(d.readDocElems(outt))
457 case typeRawDocElem:
458 out.Set(d.readRawDocElems(outt))
459 }
460 return true
461 }
462 d.readDocTo(blackHole)
463 return true
464 }
465
466 var in interface{}
467
468 switch kind {
469 case 0x01: // Float64
470 in = d.readFloat64()
471 case 0x02: // UTF-8 string
472 in = d.readStr()
473 case 0x03: // Document
474 panic("Can't happen. Handled above.")
475 case 0x04: // Array
476 outt := out.Type()
477 if setterStyle(outt) != setterNone {
478 // Skip the value so its data is handed to the setter below.
479 d.dropElem(kind)
480 break
481 }
482 for outt.Kind() == reflect.Ptr {
483 outt = outt.Elem()
484 }
485 switch outt.Kind() {
486 case reflect.Array:
487 d.readArrayDocTo(out)
488 return true
489 case reflect.Slice:
490 in = d.readSliceDoc(outt)
491 default:
492 in = d.readSliceDoc(typeSlice)
493 }
494 case 0x05: // Binary
495 b := d.readBinary()
496 if b.Kind == 0x00 || b.Kind == 0x02 {
497 in = b.Data
498 } else {
499 in = b
500 }
501 case 0x06: // Undefined (obsolete, but still seen in the wild)
502 in = Undefined
503 case 0x07: // ObjectId
504 in = ObjectId(d.readBytes(12))
505 case 0x08: // Bool
506 in = d.readBool()
507 case 0x09: // Timestamp
508 // MongoDB handles timestamps as milliseconds.
509 i := d.readInt64()
510 if i == -62135596800000 {
511 in = time.Time{} // In UTC for convenience.
512 } else {
513 in = time.Unix(i/1e3, i%1e3*1e6)
514 }
515 case 0x0A: // Nil
516 in = nil
517 case 0x0B: // RegEx
518 in = d.readRegEx()
519 case 0x0C:
520 in = DBPointer{Namespace: d.readStr(), Id: ObjectId(d.readBytes(12))}
521 case 0x0D: // JavaScript without scope
522 in = JavaScript{Code: d.readStr()}
523 case 0x0E: // Symbol
524 in = Symbol(d.readStr())
525 case 0x0F: // JavaScript with scope
526 d.i += 4 // Skip length
527 js := JavaScript{d.readStr(), make(M)}
528 d.readDocTo(reflect.ValueOf(js.Scope))
529 in = js
530 case 0x10: // Int32
531 in = int(d.readInt32())
532 case 0x11: // Mongo-specific timestamp
533 in = MongoTimestamp(d.readInt64())
534 case 0x12: // Int64
535 in = d.readInt64()
536 case 0x7F: // Max key
537 in = MaxKey
538 case 0xFF: // Min key
539 in = MinKey
540 default:
541 panic(fmt.Sprintf("Unknown element kind (0x%02X)", kind))
542 }
543
544 outt := out.Type()
545
546 if outt == typeRaw {
547 out.Set(reflect.ValueOf(Raw{kind, d.in[start:d.i]}))
548 return true
549 }
550
551 if setter := getSetter(outt, out); setter != nil {
552 err := setter.SetBSON(Raw{kind, d.in[start:d.i]})
553 if err == SetZero {
554 out.Set(reflect.Zero(outt))
555 return true
556 }
557 if err == nil {
558 return true
559 }
560 if _, ok := err.(*TypeError); !ok {
561 panic(err)
562 }
563 return false
564 }
565
566 if in == nil {
567 out.Set(reflect.Zero(outt))
568 return true
569 }
570
571 outk := outt.Kind()
572
573 // Dereference and initialize pointer if necessary.
574 first := true
575 for outk == reflect.Ptr {
576 if !out.IsNil() {
577 out = out.Elem()
578 } else {
579 elem := reflect.New(outt.Elem())
580 if first {
581 // Only set if value is compatible.
582 first = false
583 defer func(out, elem reflect.Value) {
584 if good {
585 out.Set(elem)
586 }
587 }(out, elem)
588 } else {
589 out.Set(elem)
590 }
591 out = elem
592 }
593 outt = out.Type()
594 outk = outt.Kind()
595 }
596
597 inv := reflect.ValueOf(in)
598 if outt == inv.Type() {
599 out.Set(inv)
600 return true
601 }
602
603 switch outk {
604 case reflect.Interface:
605 out.Set(inv)
606 return true
607 case reflect.String:
608 switch inv.Kind() {
609 case reflect.String:
610 out.SetString(inv.String())
611 return true
612 case reflect.Slice:
613 if b, ok := in.([]byte); ok {
614 out.SetString(string(b))
615 return true
616 }
617 case reflect.Int, reflect.Int64:
618 if outt == typeJSONNumber {
619 out.SetString(strconv.FormatInt(inv.Int(), 10))
620 return true
621 }
622 case reflect.Float64:
623 if outt == typeJSONNumber {
624 out.SetString(strconv.FormatFloat(inv.Float(), 'f', -1, 64))
625 return true
626 }
627 }
628 case reflect.Slice, reflect.Array:
629 // Remember, array (0x04) slices are built with the correct
630 // element type. If we are here, must be a cross BSON kind
631 // conversion (e.g. 0x05 unmarshalling on string).
632 if outt.Elem().Kind() != reflect.Uint8 {
633 break
634 }
635 switch inv.Kind() {
636 case reflect.String:
637 slice := []byte(inv.String())
638 out.Set(reflect.ValueOf(slice))
639 return true
640 case reflect.Slice:
641 switch outt.Kind() {
642 case reflect.Array:
643 reflect.Copy(out, inv)
644 case reflect.Slice:
645 out.SetBytes(inv.Bytes())
646 }
647 return true
648 }
649 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
650 switch inv.Kind() {
651 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
652 out.SetInt(inv.Int())
653 return true
654 case reflect.Float32, reflect.Float64:
655 out.SetInt(int64(inv.Float()))
656 return true
657 case reflect.Bool:
658 if inv.Bool() {
659 out.SetInt(1)
660 } else {
661 out.SetInt(0)
662 }
663 return true
664 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
665 panic("can't happen: no uint types in BSON (!?)")
666 }
667 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
668 switch inv.Kind() {
669 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
670 out.SetUint(uint64(inv.Int()))
671 return true
672 case reflect.Float32, reflect.Float64:
673 out.SetUint(uint64(inv.Float()))
674 return true
675 case reflect.Bool:
676 if inv.Bool() {
677 out.SetUint(1)
678 } else {
679 out.SetUint(0)
680 }
681 return true
682 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
683 panic("Can't happen. No uint types in BSON.")
684 }
685 case reflect.Float32, reflect.Float64:
686 switch inv.Kind() {
687 case reflect.Float32, reflect.Float64:
688 out.SetFloat(inv.Float())
689 return true
690 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
691 out.SetFloat(float64(inv.Int()))
692 return true
693 case reflect.Bool:
694 if inv.Bool() {
695 out.SetFloat(1)
696 } else {
697 out.SetFloat(0)
698 }
699 return true
700 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
701 panic("Can't happen. No uint types in BSON?")
702 }
703 case reflect.Bool:
704 switch inv.Kind() {
705 case reflect.Bool:
706 out.SetBool(inv.Bool())
707 return true
708 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
709 out.SetBool(inv.Int() != 0)
710 return true
711 case reflect.Float32, reflect.Float64:
712 out.SetBool(inv.Float() != 0)
713 return true
714 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
715 panic("Can't happen. No uint types in BSON?")
716 }
717 case reflect.Struct:
718 if outt == typeURL && inv.Kind() == reflect.String {
719 u, err := url.Parse(inv.String())
720 if err != nil {
721 panic(err)
722 }
723 out.Set(reflect.ValueOf(u).Elem())
724 return true
725 }
726 }
727
728 return false
729}
730
731// --------------------------------------------------------------------------
732// Parsers of basic types.
733
734func (d *decoder) readRegEx() RegEx {
735 re := RegEx{}
736 re.Pattern = d.readCStr()
737 re.Options = d.readCStr()
738 return re
739}
740
741func (d *decoder) readBinary() Binary {
742 l := d.readInt32()
743 b := Binary{}
744 b.Kind = d.readByte()
745 b.Data = d.readBytes(l)
746 if b.Kind == 0x02 && len(b.Data) >= 4 {
747 // Weird obsolete format with redundant length.
748 b.Data = b.Data[4:]
749 }
750 return b
751}
752
753func (d *decoder) readStr() string {
754 l := d.readInt32()
755 b := d.readBytes(l - 1)
756 if d.readByte() != '\x00' {
757 corrupted()
758 }
759 return string(b)
760}
761
762func (d *decoder) readCStr() string {
763 start := d.i
764 end := start
765 l := len(d.in)
766 for ; end != l; end++ {
767 if d.in[end] == '\x00' {
768 break
769 }
770 }
771 d.i = end + 1
772 if d.i > l {
773 corrupted()
774 }
775 return string(d.in[start:end])
776}
777
778func (d *decoder) readBool() bool {
779 if d.readByte() == 1 {
780 return true
781 }
782 return false
783}
784
785func (d *decoder) readFloat64() float64 {
786 return math.Float64frombits(uint64(d.readInt64()))
787}
788
789func (d *decoder) readInt32() int32 {
790 b := d.readBytes(4)
791 return int32((uint32(b[0]) << 0) |
792 (uint32(b[1]) << 8) |
793 (uint32(b[2]) << 16) |
794 (uint32(b[3]) << 24))
795}
796
797func (d *decoder) readInt64() int64 {
798 b := d.readBytes(8)
799 return int64((uint64(b[0]) << 0) |
800 (uint64(b[1]) << 8) |
801 (uint64(b[2]) << 16) |
802 (uint64(b[3]) << 24) |
803 (uint64(b[4]) << 32) |
804 (uint64(b[5]) << 40) |
805 (uint64(b[6]) << 48) |
806 (uint64(b[7]) << 56))
807}
808
809func (d *decoder) readByte() byte {
810 i := d.i
811 d.i++
812 if d.i > len(d.in) {
813 corrupted()
814 }
815 return d.in[i]
816}
817
818func (d *decoder) readBytes(length int32) []byte {
819 start := d.i
820 d.i += int(length)
821 if d.i > len(d.in) {
822 corrupted()
823 }
824 return d.in[start : start+int(length)]
825}
vendor/gopkg.in/mgo.v2/bson/encode.go+503−0
@@ -0,0 +1,503 @@
1// BSON library for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26// gobson - BSON library for Go.
27
28package bson
29
30import (
31 "encoding/json"
32 "fmt"
33 "math"
34 "net/url"
35 "reflect"
36 "strconv"
37 "time"
38)
39
40// --------------------------------------------------------------------------
41// Some internal infrastructure.
42
43var (
44 typeBinary = reflect.TypeOf(Binary{})
45 typeObjectId = reflect.TypeOf(ObjectId(""))
46 typeDBPointer = reflect.TypeOf(DBPointer{"", ObjectId("")})
47 typeSymbol = reflect.TypeOf(Symbol(""))
48 typeMongoTimestamp = reflect.TypeOf(MongoTimestamp(0))
49 typeOrderKey = reflect.TypeOf(MinKey)
50 typeDocElem = reflect.TypeOf(DocElem{})
51 typeRawDocElem = reflect.TypeOf(RawDocElem{})
52 typeRaw = reflect.TypeOf(Raw{})
53 typeURL = reflect.TypeOf(url.URL{})
54 typeTime = reflect.TypeOf(time.Time{})
55 typeString = reflect.TypeOf("")
56 typeJSONNumber = reflect.TypeOf(json.Number(""))
57)
58
59const itoaCacheSize = 32
60
61var itoaCache []string
62
63func init() {
64 itoaCache = make([]string, itoaCacheSize)
65 for i := 0; i != itoaCacheSize; i++ {
66 itoaCache[i] = strconv.Itoa(i)
67 }
68}
69
70func itoa(i int) string {
71 if i < itoaCacheSize {
72 return itoaCache[i]
73 }
74 return strconv.Itoa(i)
75}
76
77// --------------------------------------------------------------------------
78// Marshaling of the document value itself.
79
80type encoder struct {
81 out []byte
82}
83
84func (e *encoder) addDoc(v reflect.Value) {
85 for {
86 if vi, ok := v.Interface().(Getter); ok {
87 getv, err := vi.GetBSON()
88 if err != nil {
89 panic(err)
90 }
91 v = reflect.ValueOf(getv)
92 continue
93 }
94 if v.Kind() == reflect.Ptr {
95 v = v.Elem()
96 continue
97 }
98 break
99 }
100
101 if v.Type() == typeRaw {
102 raw := v.Interface().(Raw)
103 if raw.Kind != 0x03 && raw.Kind != 0x00 {
104 panic("Attempted to unmarshal Raw kind " + strconv.Itoa(int(raw.Kind)) + " as a document")
105 }
106 e.addBytes(raw.Data...)
107 return
108 }
109
110 start := e.reserveInt32()
111
112 switch v.Kind() {
113 case reflect.Map:
114 e.addMap(v)
115 case reflect.Struct:
116 e.addStruct(v)
117 case reflect.Array, reflect.Slice:
118 e.addSlice(v)
119 default:
120 panic("Can't marshal " + v.Type().String() + " as a BSON document")
121 }
122
123 e.addBytes(0)
124 e.setInt32(start, int32(len(e.out)-start))
125}
126
127func (e *encoder) addMap(v reflect.Value) {
128 for _, k := range v.MapKeys() {
129 e.addElem(k.String(), v.MapIndex(k), false)
130 }
131}
132
133func (e *encoder) addStruct(v reflect.Value) {
134 sinfo, err := getStructInfo(v.Type())
135 if err != nil {
136 panic(err)
137 }
138 var value reflect.Value
139 if sinfo.InlineMap >= 0 {
140 m := v.Field(sinfo.InlineMap)
141 if m.Len() > 0 {
142 for _, k := range m.MapKeys() {
143 ks := k.String()
144 if _, found := sinfo.FieldsMap[ks]; found {
145 panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", ks))
146 }
147 e.addElem(ks, m.MapIndex(k), false)
148 }
149 }
150 }
151 for _, info := range sinfo.FieldsList {
152 if info.Inline == nil {
153 value = v.Field(info.Num)
154 } else {
155 value = v.FieldByIndex(info.Inline)
156 }
157 if info.OmitEmpty && isZero(value) {
158 continue
159 }
160 e.addElem(info.Key, value, info.MinSize)
161 }
162}
163
164func isZero(v reflect.Value) bool {
165 switch v.Kind() {
166 case reflect.String:
167 return len(v.String()) == 0
168 case reflect.Ptr, reflect.Interface:
169 return v.IsNil()
170 case reflect.Slice:
171 return v.Len() == 0
172 case reflect.Map:
173 return v.Len() == 0
174 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
175 return v.Int() == 0
176 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
177 return v.Uint() == 0
178 case reflect.Float32, reflect.Float64:
179 return v.Float() == 0
180 case reflect.Bool:
181 return !v.Bool()
182 case reflect.Struct:
183 vt := v.Type()
184 if vt == typeTime {
185 return v.Interface().(time.Time).IsZero()
186 }
187 for i := 0; i < v.NumField(); i++ {
188 if vt.Field(i).PkgPath != "" {
189 continue // Private field
190 }
191 if !isZero(v.Field(i)) {
192 return false
193 }
194 }
195 return true
196 }
197 return false
198}
199
200func (e *encoder) addSlice(v reflect.Value) {
201 vi := v.Interface()
202 if d, ok := vi.(D); ok {
203 for _, elem := range d {
204 e.addElem(elem.Name, reflect.ValueOf(elem.Value), false)
205 }
206 return
207 }
208 if d, ok := vi.(RawD); ok {
209 for _, elem := range d {
210 e.addElem(elem.Name, reflect.ValueOf(elem.Value), false)
211 }
212 return
213 }
214 l := v.Len()
215 et := v.Type().Elem()
216 if et == typeDocElem {
217 for i := 0; i < l; i++ {
218 elem := v.Index(i).Interface().(DocElem)
219 e.addElem(elem.Name, reflect.ValueOf(elem.Value), false)
220 }
221 return
222 }
223 if et == typeRawDocElem {
224 for i := 0; i < l; i++ {
225 elem := v.Index(i).Interface().(RawDocElem)
226 e.addElem(elem.Name, reflect.ValueOf(elem.Value), false)
227 }
228 return
229 }
230 for i := 0; i < l; i++ {
231 e.addElem(itoa(i), v.Index(i), false)
232 }
233}
234
235// --------------------------------------------------------------------------
236// Marshaling of elements in a document.
237
238func (e *encoder) addElemName(kind byte, name string) {
239 e.addBytes(kind)
240 e.addBytes([]byte(name)...)
241 e.addBytes(0)
242}
243
244func (e *encoder) addElem(name string, v reflect.Value, minSize bool) {
245
246 if !v.IsValid() {
247 e.addElemName('\x0A', name)
248 return
249 }
250
251 if getter, ok := v.Interface().(Getter); ok {
252 getv, err := getter.GetBSON()
253 if err != nil {
254 panic(err)
255 }
256 e.addElem(name, reflect.ValueOf(getv), minSize)
257 return
258 }
259
260 switch v.Kind() {
261
262 case reflect.Interface:
263 e.addElem(name, v.Elem(), minSize)
264
265 case reflect.Ptr:
266 e.addElem(name, v.Elem(), minSize)
267
268 case reflect.String:
269 s := v.String()
270 switch v.Type() {
271 case typeObjectId:
272 if len(s) != 12 {
273 panic("ObjectIDs must be exactly 12 bytes long (got " +
274 strconv.Itoa(len(s)) + ")")
275 }
276 e.addElemName('\x07', name)
277 e.addBytes([]byte(s)...)
278 case typeSymbol:
279 e.addElemName('\x0E', name)
280 e.addStr(s)
281 case typeJSONNumber:
282 n := v.Interface().(json.Number)
283 if i, err := n.Int64(); err == nil {
284 e.addElemName('\x12', name)
285 e.addInt64(i)
286 } else if f, err := n.Float64(); err == nil {
287 e.addElemName('\x01', name)
288 e.addFloat64(f)
289 } else {
290 panic("failed to convert json.Number to a number: " + s)
291 }
292 default:
293 e.addElemName('\x02', name)
294 e.addStr(s)
295 }
296
297 case reflect.Float32, reflect.Float64:
298 e.addElemName('\x01', name)
299 e.addFloat64(v.Float())
300
301 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
302 u := v.Uint()
303 if int64(u) < 0 {
304 panic("BSON has no uint64 type, and value is too large to fit correctly in an int64")
305 } else if u <= math.MaxInt32 && (minSize || v.Kind() <= reflect.Uint32) {
306 e.addElemName('\x10', name)
307 e.addInt32(int32(u))
308 } else {
309 e.addElemName('\x12', name)
310 e.addInt64(int64(u))
311 }
312
313 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
314 switch v.Type() {
315 case typeMongoTimestamp:
316 e.addElemName('\x11', name)
317 e.addInt64(v.Int())
318
319 case typeOrderKey:
320 if v.Int() == int64(MaxKey) {
321 e.addElemName('\x7F', name)
322 } else {
323 e.addElemName('\xFF', name)
324 }
325
326 default:
327 i := v.Int()
328 if (minSize || v.Type().Kind() != reflect.Int64) && i >= math.MinInt32 && i <= math.MaxInt32 {
329 // It fits into an int32, encode as such.
330 e.addElemName('\x10', name)
331 e.addInt32(int32(i))
332 } else {
333 e.addElemName('\x12', name)
334 e.addInt64(i)
335 }
336 }
337
338 case reflect.Bool:
339 e.addElemName('\x08', name)
340 if v.Bool() {
341 e.addBytes(1)
342 } else {
343 e.addBytes(0)
344 }
345
346 case reflect.Map:
347 e.addElemName('\x03', name)
348 e.addDoc(v)
349
350 case reflect.Slice:
351 vt := v.Type()
352 et := vt.Elem()
353 if et.Kind() == reflect.Uint8 {
354 e.addElemName('\x05', name)
355 e.addBinary('\x00', v.Bytes())
356 } else if et == typeDocElem || et == typeRawDocElem {
357 e.addElemName('\x03', name)
358 e.addDoc(v)
359 } else {
360 e.addElemName('\x04', name)
361 e.addDoc(v)
362 }
363
364 case reflect.Array:
365 et := v.Type().Elem()
366 if et.Kind() == reflect.Uint8 {
367 e.addElemName('\x05', name)
368 if v.CanAddr() {
369 e.addBinary('\x00', v.Slice(0, v.Len()).Interface().([]byte))
370 } else {
371 n := v.Len()
372 e.addInt32(int32(n))
373 e.addBytes('\x00')
374 for i := 0; i < n; i++ {
375 el := v.Index(i)
376 e.addBytes(byte(el.Uint()))
377 }
378 }
379 } else {
380 e.addElemName('\x04', name)
381 e.addDoc(v)
382 }
383
384 case reflect.Struct:
385 switch s := v.Interface().(type) {
386
387 case Raw:
388 kind := s.Kind
389 if kind == 0x00 {
390 kind = 0x03
391 }
392 e.addElemName(kind, name)
393 e.addBytes(s.Data...)
394
395 case Binary:
396 e.addElemName('\x05', name)
397 e.addBinary(s.Kind, s.Data)
398
399 case DBPointer:
400 e.addElemName('\x0C', name)
401 e.addStr(s.Namespace)
402 if len(s.Id) != 12 {
403 panic("ObjectIDs must be exactly 12 bytes long (got " +
404 strconv.Itoa(len(s.Id)) + ")")
405 }
406 e.addBytes([]byte(s.Id)...)
407
408 case RegEx:
409 e.addElemName('\x0B', name)
410 e.addCStr(s.Pattern)
411 e.addCStr(s.Options)
412
413 case JavaScript:
414 if s.Scope == nil {
415 e.addElemName('\x0D', name)
416 e.addStr(s.Code)
417 } else {
418 e.addElemName('\x0F', name)
419 start := e.reserveInt32()
420 e.addStr(s.Code)
421 e.addDoc(reflect.ValueOf(s.Scope))
422 e.setInt32(start, int32(len(e.out)-start))
423 }
424
425 case time.Time:
426 // MongoDB handles timestamps as milliseconds.
427 e.addElemName('\x09', name)
428 e.addInt64(s.Unix()*1000 + int64(s.Nanosecond()/1e6))
429
430 case url.URL:
431 e.addElemName('\x02', name)
432 e.addStr(s.String())
433
434 case undefined:
435 e.addElemName('\x06', name)
436
437 default:
438 e.addElemName('\x03', name)
439 e.addDoc(v)
440 }
441
442 default:
443 panic("Can't marshal " + v.Type().String() + " in a BSON document")
444 }
445}
446
447// --------------------------------------------------------------------------
448// Marshaling of base types.
449
450func (e *encoder) addBinary(subtype byte, v []byte) {
451 if subtype == 0x02 {
452 // Wonder how that brilliant idea came to life. Obsolete, luckily.
453 e.addInt32(int32(len(v) + 4))
454 e.addBytes(subtype)
455 e.addInt32(int32(len(v)))
456 } else {
457 e.addInt32(int32(len(v)))
458 e.addBytes(subtype)
459 }
460 e.addBytes(v...)
461}
462
463func (e *encoder) addStr(v string) {
464 e.addInt32(int32(len(v) + 1))
465 e.addCStr(v)
466}
467
468func (e *encoder) addCStr(v string) {
469 e.addBytes([]byte(v)...)
470 e.addBytes(0)
471}
472
473func (e *encoder) reserveInt32() (pos int) {
474 pos = len(e.out)
475 e.addBytes(0, 0, 0, 0)
476 return pos
477}
478
479func (e *encoder) setInt32(pos int, v int32) {
480 e.out[pos+0] = byte(v)
481 e.out[pos+1] = byte(v >> 8)
482 e.out[pos+2] = byte(v >> 16)
483 e.out[pos+3] = byte(v >> 24)
484}
485
486func (e *encoder) addInt32(v int32) {
487 u := uint32(v)
488 e.addBytes(byte(u), byte(u>>8), byte(u>>16), byte(u>>24))
489}
490
491func (e *encoder) addInt64(v int64) {
492 u := uint64(v)
493 e.addBytes(byte(u), byte(u>>8), byte(u>>16), byte(u>>24),
494 byte(u>>32), byte(u>>40), byte(u>>48), byte(u>>56))
495}
496
497func (e *encoder) addFloat64(v float64) {
498 e.addInt64(int64(math.Float64bits(v)))
499}
500
501func (e *encoder) addBytes(v ...byte) {
502 e.out = append(e.out, v...)
503}
vendor/gopkg.in/mgo.v2/bulk.go+71−0
@@ -0,0 +1,71 @@
1package mgo
2
3// Bulk represents an operation that can be prepared with several
4// orthogonal changes before being delivered to the server.
5//
6// WARNING: This API is still experimental.
7//
8// Relevant documentation:
9//
10// http://blog.mongodb.org/post/84922794768/mongodbs-new-bulk-api
11//
12type Bulk struct {
13 c *Collection
14 ordered bool
15 inserts []interface{}
16}
17
18// BulkError holds an error returned from running a Bulk operation.
19//
20// TODO: This is private for the moment, until we understand exactly how
21// to report these multi-errors in a useful and convenient way.
22type bulkError struct {
23 err error
24}
25
26// BulkResult holds the results for a bulk operation.
27type BulkResult struct {
28 // Be conservative while we understand exactly how to report these
29 // results in a useful and convenient way, and also how to emulate
30 // them with prior servers.
31 private bool
32}
33
34func (e *bulkError) Error() string {
35 return e.err.Error()
36}
37
38// Bulk returns a value to prepare the execution of a bulk operation.
39//
40// WARNING: This API is still experimental.
41//
42func (c *Collection) Bulk() *Bulk {
43 return &Bulk{c: c, ordered: true}
44}
45
46// Unordered puts the bulk operation in unordered mode.
47//
48// In unordered mode the indvidual operations may be sent
49// out of order, which means latter operations may proceed
50// even if prior ones have failed.
51func (b *Bulk) Unordered() {
52 b.ordered = false
53}
54
55// Insert queues up the provided documents for insertion.
56func (b *Bulk) Insert(docs ...interface{}) {
57 b.inserts = append(b.inserts, docs...)
58}
59
60// Run runs all the operations queued up.
61func (b *Bulk) Run() (*BulkResult, error) {
62 op := &insertOp{b.c.FullName, b.inserts, 0}
63 if !b.ordered {
64 op.flags = 1 // ContinueOnError
65 }
66 _, err := b.c.writeQuery(op)
67 if err != nil {
68 return nil, &bulkError{err}
69 }
70 return &BulkResult{}, nil
71}
vendor/gopkg.in/mgo.v2/bulk_test.go+131−0
@@ -0,0 +1,131 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo_test
28
29import (
30 . "gopkg.in/check.v1"
31 "gopkg.in/mgo.v2"
32)
33
34func (s *S) TestBulkInsert(c *C) {
35 session, err := mgo.Dial("localhost:40001")
36 c.Assert(err, IsNil)
37 defer session.Close()
38
39 coll := session.DB("mydb").C("mycoll")
40 bulk := coll.Bulk()
41 bulk.Insert(M{"n": 1})
42 bulk.Insert(M{"n": 2}, M{"n": 3})
43 r, err := bulk.Run()
44 c.Assert(err, IsNil)
45 c.Assert(r, FitsTypeOf, &mgo.BulkResult{})
46
47 type doc struct{ N int }
48 var res []doc
49 err = coll.Find(nil).Sort("n").All(&res)
50 c.Assert(err, IsNil)
51 c.Assert(res, DeepEquals, []doc{{1}, {2}, {3}})
52}
53
54func (s *S) TestBulkInsertError(c *C) {
55 session, err := mgo.Dial("localhost:40001")
56 c.Assert(err, IsNil)
57 defer session.Close()
58
59 coll := session.DB("mydb").C("mycoll")
60 bulk := coll.Bulk()
61 bulk.Insert(M{"_id": 1}, M{"_id": 2}, M{"_id": 2}, M{"_id": 3})
62 _, err = bulk.Run()
63 c.Assert(err, ErrorMatches, ".*duplicate key.*")
64
65 type doc struct {
66 N int `_id`
67 }
68 var res []doc
69 err = coll.Find(nil).Sort("_id").All(&res)
70 c.Assert(err, IsNil)
71 c.Assert(res, DeepEquals, []doc{{1}, {2}})
72}
73
74func (s *S) TestBulkInsertErrorUnordered(c *C) {
75 session, err := mgo.Dial("localhost:40001")
76 c.Assert(err, IsNil)
77 defer session.Close()
78
79 coll := session.DB("mydb").C("mycoll")
80 bulk := coll.Bulk()
81 bulk.Unordered()
82 bulk.Insert(M{"_id": 1}, M{"_id": 2}, M{"_id": 2}, M{"_id": 3})
83 _, err = bulk.Run()
84 c.Assert(err, ErrorMatches, ".*duplicate key.*")
85
86 type doc struct {
87 N int `_id`
88 }
89 var res []doc
90 err = coll.Find(nil).Sort("_id").All(&res)
91 c.Assert(err, IsNil)
92 c.Assert(res, DeepEquals, []doc{{1}, {2}, {3}})
93}
94
95func (s *S) TestBulkInsertErrorUnorderedSplitBatch(c *C) {
96 // The server has a batch limit of 1000 documents when using write commands.
97 // This artificial limit did not exist with the old wire protocol, so to
98 // avoid compatibility issues the implementation internally split batches
99 // into the proper size and delivers them one by one. This test ensures that
100 // the behavior of unordered (that is, continue on error) remains correct
101 // when errors happen and there are batches left.
102 session, err := mgo.Dial("localhost:40001")
103 c.Assert(err, IsNil)
104 defer session.Close()
105
106 coll := session.DB("mydb").C("mycoll")
107 bulk := coll.Bulk()
108 bulk.Unordered()
109
110 const total = 4096
111 type doc struct {
112 Id int `_id`
113 }
114 docs := make([]interface{}, total)
115 for i := 0; i < total; i++ {
116 docs[i] = doc{i}
117 }
118 docs[1] = doc{0}
119 bulk.Insert(docs...)
120 _, err = bulk.Run()
121 c.Assert(err, ErrorMatches, ".*duplicate key.*")
122
123 n, err := coll.Count()
124 c.Assert(err, IsNil)
125 c.Assert(n, Equals, total-1)
126
127 var res doc
128 err = coll.FindId(1500).One(&res)
129 c.Assert(err, IsNil)
130 c.Assert(res.Id, Equals, 1500)
131}
vendor/gopkg.in/mgo.v2/cluster.go+632−0
@@ -0,0 +1,632 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29import (
30 "errors"
31 "fmt"
32 "net"
33 "sync"
34 "time"
35
36 "gopkg.in/mgo.v2/bson"
37)
38
39// ---------------------------------------------------------------------------
40// Mongo cluster encapsulation.
41//
42// A cluster enables the communication with one or more servers participating
43// in a mongo cluster. This works with individual servers, a replica set,
44// a replica pair, one or multiple mongos routers, etc.
45
46type mongoCluster struct {
47 sync.RWMutex
48 serverSynced sync.Cond
49 userSeeds []string
50 dynaSeeds []string
51 servers mongoServers
52 masters mongoServers
53 references int
54 syncing bool
55 direct bool
56 failFast bool
57 syncCount uint
58 setName string
59 cachedIndex map[string]bool
60 sync chan bool
61 dial dialer
62}
63
64func newCluster(userSeeds []string, direct, failFast bool, dial dialer, setName string) *mongoCluster {
65 cluster := &mongoCluster{
66 userSeeds: userSeeds,
67 references: 1,
68 direct: direct,
69 failFast: failFast,
70 dial: dial,
71 setName: setName,
72 }
73 cluster.serverSynced.L = cluster.RWMutex.RLocker()
74 cluster.sync = make(chan bool, 1)
75 stats.cluster(+1)
76 go cluster.syncServersLoop()
77 return cluster
78}
79
80// Acquire increases the reference count for the cluster.
81func (cluster *mongoCluster) Acquire() {
82 cluster.Lock()
83 cluster.references++
84 debugf("Cluster %p acquired (refs=%d)", cluster, cluster.references)
85 cluster.Unlock()
86}
87
88// Release decreases the reference count for the cluster. Once
89// it reaches zero, all servers will be closed.
90func (cluster *mongoCluster) Release() {
91 cluster.Lock()
92 if cluster.references == 0 {
93 panic("cluster.Release() with references == 0")
94 }
95 cluster.references--
96 debugf("Cluster %p released (refs=%d)", cluster, cluster.references)
97 if cluster.references == 0 {
98 for _, server := range cluster.servers.Slice() {
99 server.Close()
100 }
101 // Wake up the sync loop so it can die.
102 cluster.syncServers()
103 stats.cluster(-1)
104 }
105 cluster.Unlock()
106}
107
108func (cluster *mongoCluster) LiveServers() (servers []string) {
109 cluster.RLock()
110 for _, serv := range cluster.servers.Slice() {
111 servers = append(servers, serv.Addr)
112 }
113 cluster.RUnlock()
114 return servers
115}
116
117func (cluster *mongoCluster) removeServer(server *mongoServer) {
118 cluster.Lock()
119 cluster.masters.Remove(server)
120 other := cluster.servers.Remove(server)
121 cluster.Unlock()
122 if other != nil {
123 other.Close()
124 log("Removed server ", server.Addr, " from cluster.")
125 }
126 server.Close()
127}
128
129type isMasterResult struct {
130 IsMaster bool
131 Secondary bool
132 Primary string
133 Hosts []string
134 Passives []string
135 Tags bson.D
136 Msg string
137 SetName string `bson:"setName"`
138 MaxWireVersion int `bson:"maxWireVersion"`
139}
140
141func (cluster *mongoCluster) isMaster(socket *mongoSocket, result *isMasterResult) error {
142 // Monotonic let's it talk to a slave and still hold the socket.
143 session := newSession(Monotonic, cluster, 10*time.Second)
144 session.setSocket(socket)
145 err := session.Run("ismaster", result)
146 session.Close()
147 return err
148}
149
150type possibleTimeout interface {
151 Timeout() bool
152}
153
154var syncSocketTimeout = 5 * time.Second
155
156func (cluster *mongoCluster) syncServer(server *mongoServer) (info *mongoServerInfo, hosts []string, err error) {
157 var syncTimeout time.Duration
158 if raceDetector {
159 // This variable is only ever touched by tests.
160 globalMutex.Lock()
161 syncTimeout = syncSocketTimeout
162 globalMutex.Unlock()
163 } else {
164 syncTimeout = syncSocketTimeout
165 }
166
167 addr := server.Addr
168 log("SYNC Processing ", addr, "...")
169
170 // Retry a few times to avoid knocking a server down for a hiccup.
171 var result isMasterResult
172 var tryerr error
173 for retry := 0; ; retry++ {
174 if retry == 3 || retry == 1 && cluster.failFast {
175 return nil, nil, tryerr
176 }
177 if retry > 0 {
178 // Don't abuse the server needlessly if there's something actually wrong.
179 if err, ok := tryerr.(possibleTimeout); ok && err.Timeout() {
180 // Give a chance for waiters to timeout as well.
181 cluster.serverSynced.Broadcast()
182 }
183 time.Sleep(syncShortDelay)
184 }
185
186 // It's not clear what would be a good timeout here. Is it
187 // better to wait longer or to retry?
188 socket, _, err := server.AcquireSocket(0, syncTimeout)
189 if err != nil {
190 tryerr = err
191 logf("SYNC Failed to get socket to %s: %v", addr, err)
192 continue
193 }
194 err = cluster.isMaster(socket, &result)
195 socket.Release()
196 if err != nil {
197 tryerr = err
198 logf("SYNC Command 'ismaster' to %s failed: %v", addr, err)
199 continue
200 }
201 debugf("SYNC Result of 'ismaster' from %s: %#v", addr, result)
202 break
203 }
204
205 if cluster.setName != "" && result.SetName != cluster.setName {
206 logf("SYNC Server %s is not a member of replica set %q", addr, cluster.setName)
207 return nil, nil, fmt.Errorf("server %s is not a member of replica set %q", addr, cluster.setName)
208 }
209
210 if result.IsMaster {
211 debugf("SYNC %s is a master.", addr)
212 if !server.info.Master {
213 // Made an incorrect assumption above, so fix stats.
214 stats.conn(-1, false)
215 stats.conn(+1, true)
216 }
217 } else if result.Secondary {
218 debugf("SYNC %s is a slave.", addr)
219 } else if cluster.direct {
220 logf("SYNC %s in unknown state. Pretending it's a slave due to direct connection.", addr)
221 } else {
222 logf("SYNC %s is neither a master nor a slave.", addr)
223 // Let stats track it as whatever was known before.
224 return nil, nil, errors.New(addr + " is not a master nor slave")
225 }
226
227 info = &mongoServerInfo{
228 Master: result.IsMaster,
229 Mongos: result.Msg == "isdbgrid",
230 Tags: result.Tags,
231 SetName: result.SetName,
232 MaxWireVersion: result.MaxWireVersion,
233 }
234
235 hosts = make([]string, 0, 1+len(result.Hosts)+len(result.Passives))
236 if result.Primary != "" {
237 // First in the list to speed up master discovery.
238 hosts = append(hosts, result.Primary)
239 }
240 hosts = append(hosts, result.Hosts...)
241 hosts = append(hosts, result.Passives...)
242
243 debugf("SYNC %s knows about the following peers: %#v", addr, hosts)
244 return info, hosts, nil
245}
246
247type syncKind bool
248
249const (
250 completeSync syncKind = true
251 partialSync syncKind = false
252)
253
254func (cluster *mongoCluster) addServer(server *mongoServer, info *mongoServerInfo, syncKind syncKind) {
255 cluster.Lock()
256 current := cluster.servers.Search(server.ResolvedAddr)
257 if current == nil {
258 if syncKind == partialSync {
259 cluster.Unlock()
260 server.Close()
261 log("SYNC Discarding unknown server ", server.Addr, " due to partial sync.")
262 return
263 }
264 cluster.servers.Add(server)
265 if info.Master {
266 cluster.masters.Add(server)
267 log("SYNC Adding ", server.Addr, " to cluster as a master.")
268 } else {
269 log("SYNC Adding ", server.Addr, " to cluster as a slave.")
270 }
271 } else {
272 if server != current {
273 panic("addServer attempting to add duplicated server")
274 }
275 if server.Info().Master != info.Master {
276 if info.Master {
277 log("SYNC Server ", server.Addr, " is now a master.")
278 cluster.masters.Add(server)
279 } else {
280 log("SYNC Server ", server.Addr, " is now a slave.")
281 cluster.masters.Remove(server)
282 }
283 }
284 }
285 server.SetInfo(info)
286 debugf("SYNC Broadcasting availability of server %s", server.Addr)
287 cluster.serverSynced.Broadcast()
288 cluster.Unlock()
289}
290
291func (cluster *mongoCluster) getKnownAddrs() []string {
292 cluster.RLock()
293 max := len(cluster.userSeeds) + len(cluster.dynaSeeds) + cluster.servers.Len()
294 seen := make(map[string]bool, max)
295 known := make([]string, 0, max)
296
297 add := func(addr string) {
298 if _, found := seen[addr]; !found {
299 seen[addr] = true
300 known = append(known, addr)
301 }
302 }
303
304 for _, addr := range cluster.userSeeds {
305 add(addr)
306 }
307 for _, addr := range cluster.dynaSeeds {
308 add(addr)
309 }
310 for _, serv := range cluster.servers.Slice() {
311 add(serv.Addr)
312 }
313 cluster.RUnlock()
314
315 return known
316}
317
318// syncServers injects a value into the cluster.sync channel to force
319// an iteration of the syncServersLoop function.
320func (cluster *mongoCluster) syncServers() {
321 select {
322 case cluster.sync <- true:
323 default:
324 }
325}
326
327// How long to wait for a checkup of the cluster topology if nothing
328// else kicks a synchronization before that.
329const syncServersDelay = 30 * time.Second
330const syncShortDelay = 500 * time.Millisecond
331
332// syncServersLoop loops while the cluster is alive to keep its idea of
333// the server topology up-to-date. It must be called just once from
334// newCluster. The loop iterates once syncServersDelay has passed, or
335// if somebody injects a value into the cluster.sync channel to force a
336// synchronization. A loop iteration will contact all servers in
337// parallel, ask them about known peers and their own role within the
338// cluster, and then attempt to do the same with all the peers
339// retrieved.
340func (cluster *mongoCluster) syncServersLoop() {
341 for {
342 debugf("SYNC Cluster %p is starting a sync loop iteration.", cluster)
343
344 cluster.Lock()
345 if cluster.references == 0 {
346 cluster.Unlock()
347 break
348 }
349 cluster.references++ // Keep alive while syncing.
350 direct := cluster.direct
351 cluster.Unlock()
352
353 cluster.syncServersIteration(direct)
354
355 // We just synchronized, so consume any outstanding requests.
356 select {
357 case <-cluster.sync:
358 default:
359 }
360
361 cluster.Release()
362
363 // Hold off before allowing another sync. No point in
364 // burning CPU looking for down servers.
365 if !cluster.failFast {
366 time.Sleep(syncShortDelay)
367 }
368
369 cluster.Lock()
370 if cluster.references == 0 {
371 cluster.Unlock()
372 break
373 }
374 cluster.syncCount++
375 // Poke all waiters so they have a chance to timeout or
376 // restart syncing if they wish to.
377 cluster.serverSynced.Broadcast()
378 // Check if we have to restart immediately either way.
379 restart := !direct && cluster.masters.Empty() || cluster.servers.Empty()
380 cluster.Unlock()
381
382 if restart {
383 log("SYNC No masters found. Will synchronize again.")
384 time.Sleep(syncShortDelay)
385 continue
386 }
387
388 debugf("SYNC Cluster %p waiting for next requested or scheduled sync.", cluster)
389
390 // Hold off until somebody explicitly requests a synchronization
391 // or it's time to check for a cluster topology change again.
392 select {
393 case <-cluster.sync:
394 case <-time.After(syncServersDelay):
395 }
396 }
397 debugf("SYNC Cluster %p is stopping its sync loop.", cluster)
398}
399
400func (cluster *mongoCluster) server(addr string, tcpaddr *net.TCPAddr) *mongoServer {
401 cluster.RLock()
402 server := cluster.servers.Search(tcpaddr.String())
403 cluster.RUnlock()
404 if server != nil {
405 return server
406 }
407 return newServer(addr, tcpaddr, cluster.sync, cluster.dial)
408}
409
410func resolveAddr(addr string) (*net.TCPAddr, error) {
411 // This hack allows having a timeout on resolution.
412 conn, err := net.DialTimeout("udp4", addr, 10*time.Second)
413 if err != nil {
414 log("SYNC Failed to resolve server address: ", addr)
415 return nil, errors.New("failed to resolve server address: " + addr)
416 }
417 tcpaddr := (*net.TCPAddr)(conn.RemoteAddr().(*net.UDPAddr))
418 conn.Close()
419 if tcpaddr.String() != addr {
420 debug("SYNC Address ", addr, " resolved as ", tcpaddr.String())
421 }
422 return tcpaddr, nil
423}
424
425type pendingAdd struct {
426 server *mongoServer
427 info *mongoServerInfo
428}
429
430func (cluster *mongoCluster) syncServersIteration(direct bool) {
431 log("SYNC Starting full topology synchronization...")
432
433 var wg sync.WaitGroup
434 var m sync.Mutex
435 notYetAdded := make(map[string]pendingAdd)
436 addIfFound := make(map[string]bool)
437 seen := make(map[string]bool)
438 syncKind := partialSync
439
440 var spawnSync func(addr string, byMaster bool)
441 spawnSync = func(addr string, byMaster bool) {
442 wg.Add(1)
443 go func() {
444 defer wg.Done()
445
446 tcpaddr, err := resolveAddr(addr)
447 if err != nil {
448 log("SYNC Failed to start sync of ", addr, ": ", err.Error())
449 return
450 }
451 resolvedAddr := tcpaddr.String()
452
453 m.Lock()
454 if byMaster {
455 if pending, ok := notYetAdded[resolvedAddr]; ok {
456 delete(notYetAdded, resolvedAddr)
457 m.Unlock()
458 cluster.addServer(pending.server, pending.info, completeSync)
459 return
460 }
461 addIfFound[resolvedAddr] = true
462 }
463 if seen[resolvedAddr] {
464 m.Unlock()
465 return
466 }
467 seen[resolvedAddr] = true
468 m.Unlock()
469
470 server := cluster.server(addr, tcpaddr)
471 info, hosts, err := cluster.syncServer(server)
472 if err != nil {
473 cluster.removeServer(server)
474 return
475 }
476
477 m.Lock()
478 add := direct || info.Master || addIfFound[resolvedAddr]
479 if add {
480 syncKind = completeSync
481 } else {
482 notYetAdded[resolvedAddr] = pendingAdd{server, info}
483 }
484 m.Unlock()
485 if add {
486 cluster.addServer(server, info, completeSync)
487 }
488 if !direct {
489 for _, addr := range hosts {
490 spawnSync(addr, info.Master)
491 }
492 }
493 }()
494 }
495
496 knownAddrs := cluster.getKnownAddrs()
497 for _, addr := range knownAddrs {
498 spawnSync(addr, false)
499 }
500 wg.Wait()
501
502 if syncKind == completeSync {
503 logf("SYNC Synchronization was complete (got data from primary).")
504 for _, pending := range notYetAdded {
505 cluster.removeServer(pending.server)
506 }
507 } else {
508 logf("SYNC Synchronization was partial (cannot talk to primary).")
509 for _, pending := range notYetAdded {
510 cluster.addServer(pending.server, pending.info, partialSync)
511 }
512 }
513
514 cluster.Lock()
515 ml := cluster.masters.Len()
516 logf("SYNC Synchronization completed: %d master(s) and %d slave(s) alive.", ml, cluster.servers.Len()-ml)
517
518 // Update dynamic seeds, but only if we have any good servers. Otherwise,
519 // leave them alone for better chances of a successful sync in the future.
520 if syncKind == completeSync {
521 dynaSeeds := make([]string, cluster.servers.Len())
522 for i, server := range cluster.servers.Slice() {
523 dynaSeeds[i] = server.Addr
524 }
525 cluster.dynaSeeds = dynaSeeds
526 debugf("SYNC New dynamic seeds: %#v\n", dynaSeeds)
527 }
528 cluster.Unlock()
529}
530
531// AcquireSocket returns a socket to a server in the cluster. If slaveOk is
532// true, it will attempt to return a socket to a slave server. If it is
533// false, the socket will necessarily be to a master server.
534func (cluster *mongoCluster) AcquireSocket(slaveOk bool, syncTimeout time.Duration, socketTimeout time.Duration, serverTags []bson.D, poolLimit int) (s *mongoSocket, err error) {
535 var started time.Time
536 var syncCount uint
537 warnedLimit := false
538 for {
539 cluster.RLock()
540 for {
541 ml := cluster.masters.Len()
542 sl := cluster.servers.Len()
543 debugf("Cluster has %d known masters and %d known slaves.", ml, sl-ml)
544 if ml > 0 || slaveOk && sl > 0 {
545 break
546 }
547 if started.IsZero() {
548 // Initialize after fast path above.
549 started = time.Now()
550 syncCount = cluster.syncCount
551 } else if syncTimeout != 0 && started.Before(time.Now().Add(-syncTimeout)) || cluster.failFast && cluster.syncCount != syncCount {
552 cluster.RUnlock()
553 return nil, errors.New("no reachable servers")
554 }
555 log("Waiting for servers to synchronize...")
556 cluster.syncServers()
557
558 // Remember: this will release and reacquire the lock.
559 cluster.serverSynced.Wait()
560 }
561
562 var server *mongoServer
563 if slaveOk {
564 server = cluster.servers.BestFit(serverTags)
565 } else {
566 server = cluster.masters.BestFit(nil)
567 }
568 cluster.RUnlock()
569
570 if server == nil {
571 // Must have failed the requested tags. Sleep to avoid spinning.
572 time.Sleep(1e8)
573 continue
574 }
575
576 s, abended, err := server.AcquireSocket(poolLimit, socketTimeout)
577 if err == errPoolLimit {
578 if !warnedLimit {
579 warnedLimit = true
580 log("WARNING: Per-server connection limit reached.")
581 }
582 time.Sleep(100 * time.Millisecond)
583 continue
584 }
585 if err != nil {
586 cluster.removeServer(server)
587 cluster.syncServers()
588 continue
589 }
590 if abended && !slaveOk {
591 var result isMasterResult
592 err := cluster.isMaster(s, &result)
593 if err != nil || !result.IsMaster {
594 logf("Cannot confirm server %s as master (%v)", server.Addr, err)
595 s.Release()
596 cluster.syncServers()
597 time.Sleep(100 * time.Millisecond)
598 continue
599 }
600 }
601 return s, nil
602 }
603 panic("unreached")
604}
605
606func (cluster *mongoCluster) CacheIndex(cacheKey string, exists bool) {
607 cluster.Lock()
608 if cluster.cachedIndex == nil {
609 cluster.cachedIndex = make(map[string]bool)
610 }
611 if exists {
612 cluster.cachedIndex[cacheKey] = true
613 } else {
614 delete(cluster.cachedIndex, cacheKey)
615 }
616 cluster.Unlock()
617}
618
619func (cluster *mongoCluster) HasCachedIndex(cacheKey string) (result bool) {
620 cluster.RLock()
621 if cluster.cachedIndex != nil {
622 result = cluster.cachedIndex[cacheKey]
623 }
624 cluster.RUnlock()
625 return
626}
627
628func (cluster *mongoCluster) ResetIndexCache() {
629 cluster.Lock()
630 cluster.cachedIndex = make(map[string]bool)
631 cluster.Unlock()
632}
vendor/gopkg.in/mgo.v2/cluster_test.go+1657−0
@@ -0,0 +1,1657 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo_test
28
29import (
30 "fmt"
31 "io"
32 "net"
33 "strings"
34 "sync"
35 "time"
36
37 . "gopkg.in/check.v1"
38 "gopkg.in/mgo.v2"
39 "gopkg.in/mgo.v2/bson"
40)
41
42func (s *S) TestNewSession(c *C) {
43 session, err := mgo.Dial("localhost:40001")
44 c.Assert(err, IsNil)
45 defer session.Close()
46
47 // Do a dummy operation to wait for connection.
48 coll := session.DB("mydb").C("mycoll")
49 err = coll.Insert(M{"_id": 1})
50 c.Assert(err, IsNil)
51
52 // Tweak safety and query settings to ensure other has copied those.
53 session.SetSafe(nil)
54 session.SetBatch(-1)
55 other := session.New()
56 defer other.Close()
57 session.SetSafe(&mgo.Safe{})
58
59 // Clone was copied while session was unsafe, so no errors.
60 otherColl := other.DB("mydb").C("mycoll")
61 err = otherColl.Insert(M{"_id": 1})
62 c.Assert(err, IsNil)
63
64 // Original session was made safe again.
65 err = coll.Insert(M{"_id": 1})
66 c.Assert(err, NotNil)
67
68 // With New(), each session has its own socket now.
69 stats := mgo.GetStats()
70 c.Assert(stats.MasterConns, Equals, 2)
71 c.Assert(stats.SocketsInUse, Equals, 2)
72
73 // Ensure query parameters were cloned.
74 err = otherColl.Insert(M{"_id": 2})
75 c.Assert(err, IsNil)
76
77 // Ping the database to ensure the nonce has been received already.
78 c.Assert(other.Ping(), IsNil)
79
80 mgo.ResetStats()
81
82 iter := otherColl.Find(M{}).Iter()
83 c.Assert(err, IsNil)
84
85 m := M{}
86 ok := iter.Next(m)
87 c.Assert(ok, Equals, true)
88 err = iter.Close()
89 c.Assert(err, IsNil)
90
91 // If Batch(-1) is in effect, a single document must have been received.
92 stats = mgo.GetStats()
93 c.Assert(stats.ReceivedDocs, Equals, 1)
94}
95
96func (s *S) TestCloneSession(c *C) {
97 session, err := mgo.Dial("localhost:40001")
98 c.Assert(err, IsNil)
99 defer session.Close()
100
101 // Do a dummy operation to wait for connection.
102 coll := session.DB("mydb").C("mycoll")
103 err = coll.Insert(M{"_id": 1})
104 c.Assert(err, IsNil)
105
106 // Tweak safety and query settings to ensure clone is copying those.
107 session.SetSafe(nil)
108 session.SetBatch(-1)
109 clone := session.Clone()
110 defer clone.Close()
111 session.SetSafe(&mgo.Safe{})
112
113 // Clone was copied while session was unsafe, so no errors.
114 cloneColl := clone.DB("mydb").C("mycoll")
115 err = cloneColl.Insert(M{"_id": 1})
116 c.Assert(err, IsNil)
117
118 // Original session was made safe again.
119 err = coll.Insert(M{"_id": 1})
120 c.Assert(err, NotNil)
121
122 // With Clone(), same socket is shared between sessions now.
123 stats := mgo.GetStats()
124 c.Assert(stats.SocketsInUse, Equals, 1)
125 c.Assert(stats.SocketRefs, Equals, 2)
126
127 // Refreshing one of them should let the original socket go,
128 // while preserving the safety settings.
129 clone.Refresh()
130 err = cloneColl.Insert(M{"_id": 1})
131 c.Assert(err, IsNil)
132
133 // Must have used another connection now.
134 stats = mgo.GetStats()
135 c.Assert(stats.SocketsInUse, Equals, 2)
136 c.Assert(stats.SocketRefs, Equals, 2)
137
138 // Ensure query parameters were cloned.
139 err = cloneColl.Insert(M{"_id": 2})
140 c.Assert(err, IsNil)
141
142 // Ping the database to ensure the nonce has been received already.
143 c.Assert(clone.Ping(), IsNil)
144
145 mgo.ResetStats()
146
147 iter := cloneColl.Find(M{}).Iter()
148 c.Assert(err, IsNil)
149
150 m := M{}
151 ok := iter.Next(m)
152 c.Assert(ok, Equals, true)
153 err = iter.Close()
154 c.Assert(err, IsNil)
155
156 // If Batch(-1) is in effect, a single document must have been received.
157 stats = mgo.GetStats()
158 c.Assert(stats.ReceivedDocs, Equals, 1)
159}
160
161func (s *S) TestSetModeStrong(c *C) {
162 session, err := mgo.Dial("localhost:40012")
163 c.Assert(err, IsNil)
164 defer session.Close()
165
166 session.SetMode(mgo.Monotonic, false)
167 session.SetMode(mgo.Strong, false)
168
169 c.Assert(session.Mode(), Equals, mgo.Strong)
170
171 result := M{}
172 cmd := session.DB("admin").C("$cmd")
173 err = cmd.Find(M{"ismaster": 1}).One(&result)
174 c.Assert(err, IsNil)
175 c.Assert(result["ismaster"], Equals, true)
176
177 coll := session.DB("mydb").C("mycoll")
178 err = coll.Insert(M{"a": 1})
179 c.Assert(err, IsNil)
180
181 // Wait since the sync also uses sockets.
182 for len(session.LiveServers()) != 3 {
183 c.Log("Waiting for cluster sync to finish...")
184 time.Sleep(5e8)
185 }
186
187 stats := mgo.GetStats()
188 c.Assert(stats.MasterConns, Equals, 1)
189 c.Assert(stats.SlaveConns, Equals, 2)
190 c.Assert(stats.SocketsInUse, Equals, 1)
191
192 session.SetMode(mgo.Strong, true)
193
194 stats = mgo.GetStats()
195 c.Assert(stats.SocketsInUse, Equals, 0)
196}
197
198func (s *S) TestSetModeMonotonic(c *C) {
199 // Must necessarily connect to a slave, otherwise the
200 // master connection will be available first.
201 session, err := mgo.Dial("localhost:40012")
202 c.Assert(err, IsNil)
203 defer session.Close()
204
205 session.SetMode(mgo.Monotonic, false)
206
207 c.Assert(session.Mode(), Equals, mgo.Monotonic)
208
209 result := M{}
210 cmd := session.DB("admin").C("$cmd")
211 err = cmd.Find(M{"ismaster": 1}).One(&result)
212 c.Assert(err, IsNil)
213 c.Assert(result["ismaster"], Equals, false)
214
215 coll := session.DB("mydb").C("mycoll")
216 err = coll.Insert(M{"a": 1})
217 c.Assert(err, IsNil)
218
219 result = M{}
220 err = cmd.Find(M{"ismaster": 1}).One(&result)
221 c.Assert(err, IsNil)
222 c.Assert(result["ismaster"], Equals, true)
223
224 // Wait since the sync also uses sockets.
225 for len(session.LiveServers()) != 3 {
226 c.Log("Waiting for cluster sync to finish...")
227 time.Sleep(5e8)
228 }
229
230 stats := mgo.GetStats()
231 c.Assert(stats.MasterConns, Equals, 1)
232 c.Assert(stats.SlaveConns, Equals, 2)
233 c.Assert(stats.SocketsInUse, Equals, 2)
234
235 session.SetMode(mgo.Monotonic, true)
236
237 stats = mgo.GetStats()
238 c.Assert(stats.SocketsInUse, Equals, 0)
239}
240
241func (s *S) TestSetModeMonotonicAfterStrong(c *C) {
242 // Test that a strong session shifting to a monotonic
243 // one preserves the socket untouched.
244
245 session, err := mgo.Dial("localhost:40012")
246 c.Assert(err, IsNil)
247 defer session.Close()
248
249 // Insert something to force a connection to the master.
250 coll := session.DB("mydb").C("mycoll")
251 err = coll.Insert(M{"a": 1})
252 c.Assert(err, IsNil)
253
254 session.SetMode(mgo.Monotonic, false)
255
256 // Wait since the sync also uses sockets.
257 for len(session.LiveServers()) != 3 {
258 c.Log("Waiting for cluster sync to finish...")
259 time.Sleep(5e8)
260 }
261
262 // Master socket should still be reserved.
263 stats := mgo.GetStats()
264 c.Assert(stats.SocketsInUse, Equals, 1)
265
266 // Confirm it's the master even though it's Monotonic by now.
267 result := M{}
268 cmd := session.DB("admin").C("$cmd")
269 err = cmd.Find(M{"ismaster": 1}).One(&result)
270 c.Assert(err, IsNil)
271 c.Assert(result["ismaster"], Equals, true)
272}
273
274func (s *S) TestSetModeStrongAfterMonotonic(c *C) {
275 // Test that shifting from Monotonic to Strong while
276 // using a slave socket will keep the socket reserved
277 // until the master socket is necessary, so that no
278 // switch over occurs unless it's actually necessary.
279
280 // Must necessarily connect to a slave, otherwise the
281 // master connection will be available first.
282 session, err := mgo.Dial("localhost:40012")
283 c.Assert(err, IsNil)
284 defer session.Close()
285
286 session.SetMode(mgo.Monotonic, false)
287
288 // Ensure we're talking to a slave, and reserve the socket.
289 result := M{}
290 err = session.Run("ismaster", &result)
291 c.Assert(err, IsNil)
292 c.Assert(result["ismaster"], Equals, false)
293
294 // Switch to a Strong session.
295 session.SetMode(mgo.Strong, false)
296
297 // Wait since the sync also uses sockets.
298 for len(session.LiveServers()) != 3 {
299 c.Log("Waiting for cluster sync to finish...")
300 time.Sleep(5e8)
301 }
302
303 // Slave socket should still be reserved.
304 stats := mgo.GetStats()
305 c.Assert(stats.SocketsInUse, Equals, 1)
306
307 // But any operation will switch it to the master.
308 result = M{}
309 err = session.Run("ismaster", &result)
310 c.Assert(err, IsNil)
311 c.Assert(result["ismaster"], Equals, true)
312}
313
314func (s *S) TestSetModeMonotonicWriteOnIteration(c *C) {
315 // Must necessarily connect to a slave, otherwise the
316 // master connection will be available first.
317 session, err := mgo.Dial("localhost:40012")
318 c.Assert(err, IsNil)
319 defer session.Close()
320
321 session.SetMode(mgo.Monotonic, false)
322
323 c.Assert(session.Mode(), Equals, mgo.Monotonic)
324
325 coll1 := session.DB("mydb").C("mycoll1")
326 coll2 := session.DB("mydb").C("mycoll2")
327
328 ns := []int{40, 41, 42, 43, 44, 45, 46}
329 for _, n := range ns {
330 err := coll1.Insert(M{"n": n})
331 c.Assert(err, IsNil)
332 }
333
334 // Release master so we can grab a slave again.
335 session.Refresh()
336
337 // Wait until synchronization is done.
338 for {
339 n, err := coll1.Count()
340 c.Assert(err, IsNil)
341 if n == len(ns) {
342 break
343 }
344 }
345
346 iter := coll1.Find(nil).Batch(2).Iter()
347 i := 0
348 m := M{}
349 for iter.Next(&m) {
350 i++
351 if i > 3 {
352 err := coll2.Insert(M{"n": 47 + i})
353 c.Assert(err, IsNil)
354 }
355 }
356 c.Assert(i, Equals, len(ns))
357}
358
359func (s *S) TestSetModeEventual(c *C) {
360 // Must necessarily connect to a slave, otherwise the
361 // master connection will be available first.
362 session, err := mgo.Dial("localhost:40012")
363 c.Assert(err, IsNil)
364 defer session.Close()
365
366 session.SetMode(mgo.Eventual, false)
367
368 c.Assert(session.Mode(), Equals, mgo.Eventual)
369
370 result := M{}
371 err = session.Run("ismaster", &result)
372 c.Assert(err, IsNil)
373 c.Assert(result["ismaster"], Equals, false)
374
375 coll := session.DB("mydb").C("mycoll")
376 err = coll.Insert(M{"a": 1})
377 c.Assert(err, IsNil)
378
379 result = M{}
380 err = session.Run("ismaster", &result)
381 c.Assert(err, IsNil)
382 c.Assert(result["ismaster"], Equals, false)
383
384 // Wait since the sync also uses sockets.
385 for len(session.LiveServers()) != 3 {
386 c.Log("Waiting for cluster sync to finish...")
387 time.Sleep(5e8)
388 }
389
390 stats := mgo.GetStats()
391 c.Assert(stats.MasterConns, Equals, 1)
392 c.Assert(stats.SlaveConns, Equals, 2)
393 c.Assert(stats.SocketsInUse, Equals, 0)
394}
395
396func (s *S) TestSetModeEventualAfterStrong(c *C) {
397 // Test that a strong session shifting to an eventual
398 // one preserves the socket untouched.
399
400 session, err := mgo.Dial("localhost:40012")
401 c.Assert(err, IsNil)
402 defer session.Close()
403
404 // Insert something to force a connection to the master.
405 coll := session.DB("mydb").C("mycoll")
406 err = coll.Insert(M{"a": 1})
407 c.Assert(err, IsNil)
408
409 session.SetMode(mgo.Eventual, false)
410
411 // Wait since the sync also uses sockets.
412 for len(session.LiveServers()) != 3 {
413 c.Log("Waiting for cluster sync to finish...")
414 time.Sleep(5e8)
415 }
416
417 // Master socket should still be reserved.
418 stats := mgo.GetStats()
419 c.Assert(stats.SocketsInUse, Equals, 1)
420
421 // Confirm it's the master even though it's Eventual by now.
422 result := M{}
423 cmd := session.DB("admin").C("$cmd")
424 err = cmd.Find(M{"ismaster": 1}).One(&result)
425 c.Assert(err, IsNil)
426 c.Assert(result["ismaster"], Equals, true)
427
428 session.SetMode(mgo.Eventual, true)
429
430 stats = mgo.GetStats()
431 c.Assert(stats.SocketsInUse, Equals, 0)
432}
433
434func (s *S) TestPrimaryShutdownStrong(c *C) {
435 if *fast {
436 c.Skip("-fast")
437 }
438
439 session, err := mgo.Dial("localhost:40021")
440 c.Assert(err, IsNil)
441 defer session.Close()
442
443 // With strong consistency, this will open a socket to the master.
444 result := &struct{ Host string }{}
445 err = session.Run("serverStatus", result)
446 c.Assert(err, IsNil)
447
448 // Kill the master.
449 host := result.Host
450 s.Stop(host)
451
452 // This must fail, since the connection was broken.
453 err = session.Run("serverStatus", result)
454 c.Assert(err, Equals, io.EOF)
455
456 // With strong consistency, it fails again until reset.
457 err = session.Run("serverStatus", result)
458 c.Assert(err, Equals, io.EOF)
459
460 session.Refresh()
461
462 // Now we should be able to talk to the new master.
463 // Increase the timeout since this may take quite a while.
464 session.SetSyncTimeout(3 * time.Minute)
465
466 err = session.Run("serverStatus", result)
467 c.Assert(err, IsNil)
468 c.Assert(result.Host, Not(Equals), host)
469
470 // Insert some data to confirm it's indeed a master.
471 err = session.DB("mydb").C("mycoll").Insert(M{"n": 42})
472 c.Assert(err, IsNil)
473}
474
475func (s *S) TestPrimaryHiccup(c *C) {
476 if *fast {
477 c.Skip("-fast")
478 }
479
480 session, err := mgo.Dial("localhost:40021")
481 c.Assert(err, IsNil)
482 defer session.Close()
483
484 // With strong consistency, this will open a socket to the master.
485 result := &struct{ Host string }{}
486 err = session.Run("serverStatus", result)
487 c.Assert(err, IsNil)
488
489 // Establish a few extra sessions to create spare sockets to
490 // the master. This increases a bit the chances of getting an
491 // incorrect cached socket.
492 var sessions []*mgo.Session
493 for i := 0; i < 20; i++ {
494 sessions = append(sessions, session.Copy())
495 err = sessions[len(sessions)-1].Run("serverStatus", result)
496 c.Assert(err, IsNil)
497 }
498 for i := range sessions {
499 sessions[i].Close()
500 }
501
502 // Kill the master, but bring it back immediatelly.
503 host := result.Host
504 s.Stop(host)
505 s.StartAll()
506
507 // This must fail, since the connection was broken.
508 err = session.Run("serverStatus", result)
509 c.Assert(err, Equals, io.EOF)
510
511 // With strong consistency, it fails again until reset.
512 err = session.Run("serverStatus", result)
513 c.Assert(err, Equals, io.EOF)
514
515 session.Refresh()
516
517 // Now we should be able to talk to the new master.
518 // Increase the timeout since this may take quite a while.
519 session.SetSyncTimeout(3 * time.Minute)
520
521 // Insert some data to confirm it's indeed a master.
522 err = session.DB("mydb").C("mycoll").Insert(M{"n": 42})
523 c.Assert(err, IsNil)
524}
525
526func (s *S) TestPrimaryShutdownMonotonic(c *C) {
527 if *fast {
528 c.Skip("-fast")
529 }
530
531 session, err := mgo.Dial("localhost:40021")
532 c.Assert(err, IsNil)
533 defer session.Close()
534
535 session.SetMode(mgo.Monotonic, true)
536
537 // Insert something to force a switch to the master.
538 coll := session.DB("mydb").C("mycoll")
539 err = coll.Insert(M{"a": 1})
540 c.Assert(err, IsNil)
541
542 // Wait a bit for this to be synchronized to slaves.
543 time.Sleep(3 * time.Second)
544
545 result := &struct{ Host string }{}
546 err = session.Run("serverStatus", result)
547 c.Assert(err, IsNil)
548
549 // Kill the master.
550 host := result.Host
551 s.Stop(host)
552
553 // This must fail, since the connection was broken.
554 err = session.Run("serverStatus", result)
555 c.Assert(err, Equals, io.EOF)
556
557 // With monotonic consistency, it fails again until reset.
558 err = session.Run("serverStatus", result)
559 c.Assert(err, Equals, io.EOF)
560
561 session.Refresh()
562
563 // Now we should be able to talk to the new master.
564 err = session.Run("serverStatus", result)
565 c.Assert(err, IsNil)
566 c.Assert(result.Host, Not(Equals), host)
567}
568
569func (s *S) TestPrimaryShutdownMonotonicWithSlave(c *C) {
570 if *fast {
571 c.Skip("-fast")
572 }
573
574 session, err := mgo.Dial("localhost:40021")
575 c.Assert(err, IsNil)
576 defer session.Close()
577
578 ssresult := &struct{ Host string }{}
579 imresult := &struct{ IsMaster bool }{}
580
581 // Figure the master while still using the strong session.
582 err = session.Run("serverStatus", ssresult)
583 c.Assert(err, IsNil)
584 err = session.Run("isMaster", imresult)
585 c.Assert(err, IsNil)
586 master := ssresult.Host
587 c.Assert(imresult.IsMaster, Equals, true, Commentf("%s is not the master", master))
588
589 // Create new monotonic session with an explicit address to ensure
590 // a slave is synchronized before the master, otherwise a connection
591 // with the master may be used below for lack of other options.
592 var addr string
593 switch {
594 case strings.HasSuffix(ssresult.Host, ":40021"):
595 addr = "localhost:40022"
596 case strings.HasSuffix(ssresult.Host, ":40022"):
597 addr = "localhost:40021"
598 case strings.HasSuffix(ssresult.Host, ":40023"):
599 addr = "localhost:40021"
600 default:
601 c.Fatal("Unknown host: ", ssresult.Host)
602 }
603
604 session, err = mgo.Dial(addr)
605 c.Assert(err, IsNil)
606 defer session.Close()
607
608 session.SetMode(mgo.Monotonic, true)
609
610 // Check the address of the socket associated with the monotonic session.
611 c.Log("Running serverStatus and isMaster with monotonic session")
612 err = session.Run("serverStatus", ssresult)
613 c.Assert(err, IsNil)
614 err = session.Run("isMaster", imresult)
615 c.Assert(err, IsNil)
616 slave := ssresult.Host
617 c.Assert(imresult.IsMaster, Equals, false, Commentf("%s is not a slave", slave))
618
619 c.Assert(master, Not(Equals), slave)
620
621 // Kill the master.
622 s.Stop(master)
623
624 // Session must still be good, since we were talking to a slave.
625 err = session.Run("serverStatus", ssresult)
626 c.Assert(err, IsNil)
627
628 c.Assert(ssresult.Host, Equals, slave,
629 Commentf("Monotonic session moved from %s to %s", slave, ssresult.Host))
630
631 // If we try to insert something, it'll have to hold until the new
632 // master is available to move the connection, and work correctly.
633 coll := session.DB("mydb").C("mycoll")
634 err = coll.Insert(M{"a": 1})
635 c.Assert(err, IsNil)
636
637 // Must now be talking to the new master.
638 err = session.Run("serverStatus", ssresult)
639 c.Assert(err, IsNil)
640 err = session.Run("isMaster", imresult)
641 c.Assert(err, IsNil)
642 c.Assert(imresult.IsMaster, Equals, true, Commentf("%s is not the master", master))
643
644 // ... which is not the old one, since it's still dead.
645 c.Assert(ssresult.Host, Not(Equals), master)
646}
647
648func (s *S) TestPrimaryShutdownEventual(c *C) {
649 if *fast {
650 c.Skip("-fast")
651 }
652
653 session, err := mgo.Dial("localhost:40021")
654 c.Assert(err, IsNil)
655 defer session.Close()
656
657 result := &struct{ Host string }{}
658 err = session.Run("serverStatus", result)
659 c.Assert(err, IsNil)
660 master := result.Host
661
662 session.SetMode(mgo.Eventual, true)
663
664 // Should connect to the master when needed.
665 coll := session.DB("mydb").C("mycoll")
666 err = coll.Insert(M{"a": 1})
667 c.Assert(err, IsNil)
668
669 // Wait a bit for this to be synchronized to slaves.
670 time.Sleep(3 * time.Second)
671
672 // Kill the master.
673 s.Stop(master)
674
675 // Should still work, with the new master now.
676 coll = session.DB("mydb").C("mycoll")
677 err = coll.Insert(M{"a": 1})
678 c.Assert(err, IsNil)
679
680 err = session.Run("serverStatus", result)
681 c.Assert(err, IsNil)
682 c.Assert(result.Host, Not(Equals), master)
683}
684
685func (s *S) TestPreserveSocketCountOnSync(c *C) {
686 if *fast {
687 c.Skip("-fast")
688 }
689
690 session, err := mgo.Dial("localhost:40011")
691 c.Assert(err, IsNil)
692 defer session.Close()
693
694 stats := mgo.GetStats()
695 for stats.MasterConns+stats.SlaveConns != 3 {
696 stats = mgo.GetStats()
697 c.Log("Waiting for all connections to be established...")
698 time.Sleep(5e8)
699 }
700
701 c.Assert(stats.SocketsAlive, Equals, 3)
702
703 // Kill the master (with rs1, 'a' is always the master).
704 s.Stop("localhost:40011")
705
706 // Wait for the logic to run for a bit and bring it back.
707 startedAll := make(chan bool)
708 go func() {
709 time.Sleep(5e9)
710 s.StartAll()
711 startedAll <- true
712 }()
713
714 // Do not allow the test to return before the goroutine above is done.
715 defer func() {
716 <-startedAll
717 }()
718
719 // Do an action to kick the resync logic in, and also to
720 // wait until the cluster recognizes the server is back.
721 result := struct{ Ok bool }{}
722 err = session.Run("getLastError", &result)
723 c.Assert(err, IsNil)
724 c.Assert(result.Ok, Equals, true)
725
726 for i := 0; i != 20; i++ {
727 stats = mgo.GetStats()
728 if stats.SocketsAlive == 3 {
729 break
730 }
731 c.Logf("Waiting for 3 sockets alive, have %d", stats.SocketsAlive)
732 time.Sleep(5e8)
733 }
734
735 // Ensure the number of sockets is preserved after syncing.
736 stats = mgo.GetStats()
737 c.Assert(stats.SocketsAlive, Equals, 3)
738 c.Assert(stats.SocketsInUse, Equals, 1)
739 c.Assert(stats.SocketRefs, Equals, 1)
740}
741
742// Connect to the master of a deployment with a single server,
743// run an insert, and then ensure the insert worked and that a
744// single connection was established.
745func (s *S) TestTopologySyncWithSingleMaster(c *C) {
746 // Use hostname here rather than IP, to make things trickier.
747 session, err := mgo.Dial("localhost:40001")
748 c.Assert(err, IsNil)
749 defer session.Close()
750
751 coll := session.DB("mydb").C("mycoll")
752 err = coll.Insert(M{"a": 1, "b": 2})
753 c.Assert(err, IsNil)
754
755 // One connection used for discovery. Master socket recycled for
756 // insert. Socket is reserved after insert.
757 stats := mgo.GetStats()
758 c.Assert(stats.MasterConns, Equals, 1)
759 c.Assert(stats.SlaveConns, Equals, 0)
760 c.Assert(stats.SocketsInUse, Equals, 1)
761
762 // Refresh session and socket must be released.
763 session.Refresh()
764 stats = mgo.GetStats()
765 c.Assert(stats.SocketsInUse, Equals, 0)
766}
767
768func (s *S) TestTopologySyncWithSlaveSeed(c *C) {
769 // That's supposed to be a slave. Must run discovery
770 // and find out master to insert successfully.
771 session, err := mgo.Dial("localhost:40012")
772 c.Assert(err, IsNil)
773 defer session.Close()
774
775 coll := session.DB("mydb").C("mycoll")
776 coll.Insert(M{"a": 1, "b": 2})
777
778 result := struct{ Ok bool }{}
779 err = session.Run("getLastError", &result)
780 c.Assert(err, IsNil)
781 c.Assert(result.Ok, Equals, true)
782
783 // One connection to each during discovery. Master
784 // socket recycled for insert.
785 stats := mgo.GetStats()
786 c.Assert(stats.MasterConns, Equals, 1)
787 c.Assert(stats.SlaveConns, Equals, 2)
788
789 // Only one socket reference alive, in the master socket owned
790 // by the above session.
791 c.Assert(stats.SocketsInUse, Equals, 1)
792
793 // Refresh it, and it must be gone.
794 session.Refresh()
795 stats = mgo.GetStats()
796 c.Assert(stats.SocketsInUse, Equals, 0)
797}
798
799func (s *S) TestSyncTimeout(c *C) {
800 if *fast {
801 c.Skip("-fast")
802 }
803
804 session, err := mgo.Dial("localhost:40001")
805 c.Assert(err, IsNil)
806 defer session.Close()
807
808 s.Stop("localhost:40001")
809
810 timeout := 3 * time.Second
811 session.SetSyncTimeout(timeout)
812 started := time.Now()
813
814 // Do something.
815 result := struct{ Ok bool }{}
816 err = session.Run("getLastError", &result)
817 c.Assert(err, ErrorMatches, "no reachable servers")
818 c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
819 c.Assert(started.After(time.Now().Add(-timeout*2)), Equals, true)
820}
821
822func (s *S) TestDialWithTimeout(c *C) {
823 if *fast {
824 c.Skip("-fast")
825 }
826
827 timeout := 2 * time.Second
828 started := time.Now()
829
830 // 40009 isn't used by the test servers.
831 session, err := mgo.DialWithTimeout("localhost:40009", timeout)
832 if session != nil {
833 session.Close()
834 }
835 c.Assert(err, ErrorMatches, "no reachable servers")
836 c.Assert(session, IsNil)
837 c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
838 c.Assert(started.After(time.Now().Add(-timeout*2)), Equals, true)
839}
840
841func (s *S) TestSocketTimeout(c *C) {
842 if *fast {
843 c.Skip("-fast")
844 }
845
846 session, err := mgo.Dial("localhost:40001")
847 c.Assert(err, IsNil)
848 defer session.Close()
849
850 s.Freeze("localhost:40001")
851
852 timeout := 3 * time.Second
853 session.SetSocketTimeout(timeout)
854 started := time.Now()
855
856 // Do something.
857 result := struct{ Ok bool }{}
858 err = session.Run("getLastError", &result)
859 c.Assert(err, ErrorMatches, ".*: i/o timeout")
860 c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
861 c.Assert(started.After(time.Now().Add(-timeout*2)), Equals, true)
862}
863
864func (s *S) TestSocketTimeoutOnDial(c *C) {
865 if *fast {
866 c.Skip("-fast")
867 }
868
869 timeout := 1 * time.Second
870
871 defer mgo.HackSyncSocketTimeout(timeout)()
872
873 s.Freeze("localhost:40001")
874
875 started := time.Now()
876
877 session, err := mgo.DialWithTimeout("localhost:40001", timeout)
878 c.Assert(err, ErrorMatches, "no reachable servers")
879 c.Assert(session, IsNil)
880
881 c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
882 c.Assert(started.After(time.Now().Add(-20*time.Second)), Equals, true)
883}
884
885func (s *S) TestSocketTimeoutOnInactiveSocket(c *C) {
886 if *fast {
887 c.Skip("-fast")
888 }
889
890 session, err := mgo.Dial("localhost:40001")
891 c.Assert(err, IsNil)
892 defer session.Close()
893
894 timeout := 2 * time.Second
895 session.SetSocketTimeout(timeout)
896
897 // Do something that relies on the timeout and works.
898 c.Assert(session.Ping(), IsNil)
899
900 // Freeze and wait for the timeout to go by.
901 s.Freeze("localhost:40001")
902 time.Sleep(timeout + 500*time.Millisecond)
903 s.Thaw("localhost:40001")
904
905 // Do something again. The timeout above should not have killed
906 // the socket as there was nothing to be done.
907 c.Assert(session.Ping(), IsNil)
908}
909
910func (s *S) TestDialWithReplicaSetName(c *C) {
911 seedLists := [][]string{
912 // rs1 primary and rs2 primary
913 []string{"localhost:40011", "localhost:40021"},
914 // rs1 primary and rs2 secondary
915 []string{"localhost:40011", "localhost:40022"},
916 // rs1 secondary and rs2 primary
917 []string{"localhost:40012", "localhost:40021"},
918 // rs1 secondary and rs2 secondary
919 []string{"localhost:40012", "localhost:40022"},
920 }
921
922 rs2Members := []string{":40021", ":40022", ":40023"}
923
924 verifySyncedServers := func(session *mgo.Session, numServers int) {
925 // wait for the server(s) to be synced
926 for len(session.LiveServers()) != numServers {
927 c.Log("Waiting for cluster sync to finish...")
928 time.Sleep(5e8)
929 }
930
931 // ensure none of the rs2 set members are communicated with
932 for _, addr := range session.LiveServers() {
933 for _, rs2Member := range rs2Members {
934 c.Assert(strings.HasSuffix(addr, rs2Member), Equals, false)
935 }
936 }
937 }
938
939 // only communication with rs1 members is expected
940 for _, seedList := range seedLists {
941 info := mgo.DialInfo{
942 Addrs: seedList,
943 Timeout: 5 * time.Second,
944 ReplicaSetName: "rs1",
945 }
946
947 session, err := mgo.DialWithInfo(&info)
948 c.Assert(err, IsNil)
949 verifySyncedServers(session, 3)
950 session.Close()
951
952 info.Direct = true
953 session, err = mgo.DialWithInfo(&info)
954 c.Assert(err, IsNil)
955 verifySyncedServers(session, 1)
956 session.Close()
957
958 connectionUrl := fmt.Sprintf("mongodb://%v/?replicaSet=rs1", strings.Join(seedList, ","))
959 session, err = mgo.Dial(connectionUrl)
960 c.Assert(err, IsNil)
961 verifySyncedServers(session, 3)
962 session.Close()
963
964 connectionUrl += "&connect=direct"
965 session, err = mgo.Dial(connectionUrl)
966 c.Assert(err, IsNil)
967 verifySyncedServers(session, 1)
968 session.Close()
969 }
970
971}
972
973func (s *S) TestDirect(c *C) {
974 session, err := mgo.Dial("localhost:40012?connect=direct")
975 c.Assert(err, IsNil)
976 defer session.Close()
977
978 // We know that server is a slave.
979 session.SetMode(mgo.Monotonic, true)
980
981 result := &struct{ Host string }{}
982 err = session.Run("serverStatus", result)
983 c.Assert(err, IsNil)
984 c.Assert(strings.HasSuffix(result.Host, ":40012"), Equals, true)
985
986 stats := mgo.GetStats()
987 c.Assert(stats.SocketsAlive, Equals, 1)
988 c.Assert(stats.SocketsInUse, Equals, 1)
989 c.Assert(stats.SocketRefs, Equals, 1)
990
991 // We've got no master, so it'll timeout.
992 session.SetSyncTimeout(5e8 * time.Nanosecond)
993
994 coll := session.DB("mydb").C("mycoll")
995 err = coll.Insert(M{"test": 1})
996 c.Assert(err, ErrorMatches, "no reachable servers")
997
998 // Writing to the local database is okay.
999 coll = session.DB("local").C("mycoll")
1000 defer coll.RemoveAll(nil)
1001 id := bson.NewObjectId()
1002 err = coll.Insert(M{"_id": id})
1003 c.Assert(err, IsNil)
1004
1005 // Data was stored in the right server.
1006 n, err := coll.Find(M{"_id": id}).Count()
1007 c.Assert(err, IsNil)
1008 c.Assert(n, Equals, 1)
1009
1010 // Server hasn't changed.
1011 result.Host = ""
1012 err = session.Run("serverStatus", result)
1013 c.Assert(err, IsNil)
1014 c.Assert(strings.HasSuffix(result.Host, ":40012"), Equals, true)
1015}
1016
1017func (s *S) TestDirectToUnknownStateMember(c *C) {
1018 session, err := mgo.Dial("localhost:40041?connect=direct")
1019 c.Assert(err, IsNil)
1020 defer session.Close()
1021
1022 session.SetMode(mgo.Monotonic, true)
1023
1024 result := &struct{ Host string }{}
1025 err = session.Run("serverStatus", result)
1026 c.Assert(err, IsNil)
1027 c.Assert(strings.HasSuffix(result.Host, ":40041"), Equals, true)
1028
1029 // We've got no master, so it'll timeout.
1030 session.SetSyncTimeout(5e8 * time.Nanosecond)
1031
1032 coll := session.DB("mydb").C("mycoll")
1033 err = coll.Insert(M{"test": 1})
1034 c.Assert(err, ErrorMatches, "no reachable servers")
1035
1036 // Slave is still reachable.
1037 result.Host = ""
1038 err = session.Run("serverStatus", result)
1039 c.Assert(err, IsNil)
1040 c.Assert(strings.HasSuffix(result.Host, ":40041"), Equals, true)
1041}
1042
1043func (s *S) TestFailFast(c *C) {
1044 info := mgo.DialInfo{
1045 Addrs: []string{"localhost:99999"},
1046 Timeout: 5 * time.Second,
1047 FailFast: true,
1048 }
1049
1050 started := time.Now()
1051
1052 _, err := mgo.DialWithInfo(&info)
1053 c.Assert(err, ErrorMatches, "no reachable servers")
1054
1055 c.Assert(started.After(time.Now().Add(-time.Second)), Equals, true)
1056}
1057
1058type OpCounters struct {
1059 Insert int
1060 Query int
1061 Update int
1062 Delete int
1063 GetMore int
1064 Command int
1065}
1066
1067func getOpCounters(server string) (c *OpCounters, err error) {
1068 session, err := mgo.Dial(server + "?connect=direct")
1069 if err != nil {
1070 return nil, err
1071 }
1072 defer session.Close()
1073 session.SetMode(mgo.Monotonic, true)
1074 result := struct{ OpCounters }{}
1075 err = session.Run("serverStatus", &result)
1076 return &result.OpCounters, err
1077}
1078
1079func (s *S) TestMonotonicSlaveOkFlagWithMongos(c *C) {
1080 session, err := mgo.Dial("localhost:40021")
1081 c.Assert(err, IsNil)
1082 defer session.Close()
1083
1084 ssresult := &struct{ Host string }{}
1085 imresult := &struct{ IsMaster bool }{}
1086
1087 // Figure the master while still using the strong session.
1088 err = session.Run("serverStatus", ssresult)
1089 c.Assert(err, IsNil)
1090 err = session.Run("isMaster", imresult)
1091 c.Assert(err, IsNil)
1092 master := ssresult.Host
1093 c.Assert(imresult.IsMaster, Equals, true, Commentf("%s is not the master", master))
1094
1095 // Collect op counters for everyone.
1096 opc21a, err := getOpCounters("localhost:40021")
1097 c.Assert(err, IsNil)
1098 opc22a, err := getOpCounters("localhost:40022")
1099 c.Assert(err, IsNil)
1100 opc23a, err := getOpCounters("localhost:40023")
1101 c.Assert(err, IsNil)
1102
1103 // Do a SlaveOk query through MongoS
1104
1105 mongos, err := mgo.Dial("localhost:40202")
1106 c.Assert(err, IsNil)
1107 defer mongos.Close()
1108
1109 mongos.SetMode(mgo.Monotonic, true)
1110
1111 coll := mongos.DB("mydb").C("mycoll")
1112 result := &struct{}{}
1113 for i := 0; i != 5; i++ {
1114 err := coll.Find(nil).One(result)
1115 c.Assert(err, Equals, mgo.ErrNotFound)
1116 }
1117
1118 // Collect op counters for everyone again.
1119 opc21b, err := getOpCounters("localhost:40021")
1120 c.Assert(err, IsNil)
1121 opc22b, err := getOpCounters("localhost:40022")
1122 c.Assert(err, IsNil)
1123 opc23b, err := getOpCounters("localhost:40023")
1124 c.Assert(err, IsNil)
1125
1126 masterPort := master[strings.Index(master, ":")+1:]
1127
1128 var masterDelta, slaveDelta int
1129 switch masterPort {
1130 case "40021":
1131 masterDelta = opc21b.Query - opc21a.Query
1132 slaveDelta = (opc22b.Query - opc22a.Query) + (opc23b.Query - opc23a.Query)
1133 case "40022":
1134 masterDelta = opc22b.Query - opc22a.Query
1135 slaveDelta = (opc21b.Query - opc21a.Query) + (opc23b.Query - opc23a.Query)
1136 case "40023":
1137 masterDelta = opc23b.Query - opc23a.Query
1138 slaveDelta = (opc21b.Query - opc21a.Query) + (opc22b.Query - opc22a.Query)
1139 default:
1140 c.Fatal("Uh?")
1141 }
1142
1143 c.Check(masterDelta, Equals, 0) // Just the counting itself.
1144 c.Check(slaveDelta, Equals, 5) // The counting for both, plus 5 queries above.
1145}
1146
1147func (s *S) TestRemovalOfClusterMember(c *C) {
1148 if *fast {
1149 c.Skip("-fast")
1150 }
1151
1152 master, err := mgo.Dial("localhost:40021")
1153 c.Assert(err, IsNil)
1154 defer master.Close()
1155
1156 // Wait for cluster to fully sync up.
1157 for i := 0; i < 10; i++ {
1158 if len(master.LiveServers()) == 3 {
1159 break
1160 }
1161 time.Sleep(5e8)
1162 }
1163 if len(master.LiveServers()) != 3 {
1164 c.Fatalf("Test started with bad cluster state: %v", master.LiveServers())
1165 }
1166
1167 result := &struct {
1168 IsMaster bool
1169 Me string
1170 }{}
1171 slave := master.Copy()
1172 slave.SetMode(mgo.Monotonic, true) // Monotonic can hold a non-master socket persistently.
1173 err = slave.Run("isMaster", result)
1174 c.Assert(err, IsNil)
1175 c.Assert(result.IsMaster, Equals, false)
1176 slaveAddr := result.Me
1177
1178 defer func() {
1179 master.Refresh()
1180 master.Run(bson.D{{"$eval", `rs.add("` + slaveAddr + `")`}}, nil)
1181 master.Close()
1182 slave.Close()
1183 }()
1184
1185 c.Logf("========== Removing slave: %s ==========", slaveAddr)
1186
1187 master.Run(bson.D{{"$eval", `rs.remove("` + slaveAddr + `")`}}, nil)
1188
1189 master.Refresh()
1190
1191 // Give the cluster a moment to catch up by doing a roundtrip to the master.
1192 err = master.Ping()
1193 c.Assert(err, IsNil)
1194
1195 time.Sleep(3e9)
1196
1197 // This must fail since the slave has been taken off the cluster.
1198 err = slave.Ping()
1199 c.Assert(err, NotNil)
1200
1201 for i := 0; i < 15; i++ {
1202 if len(master.LiveServers()) == 2 {
1203 break
1204 }
1205 time.Sleep(time.Second)
1206 }
1207 live := master.LiveServers()
1208 if len(live) != 2 {
1209 c.Errorf("Removed server still considered live: %#s", live)
1210 }
1211
1212 c.Log("========== Test succeeded. ==========")
1213}
1214
1215func (s *S) TestPoolLimitSimple(c *C) {
1216 for test := 0; test < 2; test++ {
1217 var session *mgo.Session
1218 var err error
1219 if test == 0 {
1220 session, err = mgo.Dial("localhost:40001")
1221 c.Assert(err, IsNil)
1222 session.SetPoolLimit(1)
1223 } else {
1224 session, err = mgo.Dial("localhost:40001?maxPoolSize=1")
1225 c.Assert(err, IsNil)
1226 }
1227 defer session.Close()
1228
1229 // Put one socket in use.
1230 c.Assert(session.Ping(), IsNil)
1231
1232 done := make(chan time.Duration)
1233
1234 // Now block trying to get another one due to the pool limit.
1235 go func() {
1236 copy := session.Copy()
1237 defer copy.Close()
1238 started := time.Now()
1239 c.Check(copy.Ping(), IsNil)
1240 done <- time.Now().Sub(started)
1241 }()
1242
1243 time.Sleep(300 * time.Millisecond)
1244
1245 // Put the one socket back in the pool, freeing it for the copy.
1246 session.Refresh()
1247 delay := <-done
1248 c.Assert(delay > 300*time.Millisecond, Equals, true, Commentf("Delay: %s", delay))
1249 }
1250}
1251
1252func (s *S) TestPoolLimitMany(c *C) {
1253 if *fast {
1254 c.Skip("-fast")
1255 }
1256
1257 session, err := mgo.Dial("localhost:40011")
1258 c.Assert(err, IsNil)
1259 defer session.Close()
1260
1261 stats := mgo.GetStats()
1262 for stats.MasterConns+stats.SlaveConns != 3 {
1263 stats = mgo.GetStats()
1264 c.Log("Waiting for all connections to be established...")
1265 time.Sleep(500 * time.Millisecond)
1266 }
1267 c.Assert(stats.SocketsAlive, Equals, 3)
1268
1269 const poolLimit = 64
1270 session.SetPoolLimit(poolLimit)
1271
1272 // Consume the whole limit for the master.
1273 var master []*mgo.Session
1274 for i := 0; i < poolLimit; i++ {
1275 s := session.Copy()
1276 defer s.Close()
1277 c.Assert(s.Ping(), IsNil)
1278 master = append(master, s)
1279 }
1280
1281 before := time.Now()
1282 go func() {
1283 time.Sleep(3e9)
1284 master[0].Refresh()
1285 }()
1286
1287 // Then, a single ping must block, since it would need another
1288 // connection to the master, over the limit. Once the goroutine
1289 // above releases its socket, it should move on.
1290 session.Ping()
1291 delay := time.Now().Sub(before)
1292 c.Assert(delay > 3e9, Equals, true)
1293 c.Assert(delay < 6e9, Equals, true)
1294}
1295
1296func (s *S) TestSetModeEventualIterBug(c *C) {
1297 session1, err := mgo.Dial("localhost:40011")
1298 c.Assert(err, IsNil)
1299 defer session1.Close()
1300
1301 session1.SetMode(mgo.Eventual, false)
1302
1303 coll1 := session1.DB("mydb").C("mycoll")
1304
1305 const N = 100
1306 for i := 0; i < N; i++ {
1307 err = coll1.Insert(M{"_id": i})
1308 c.Assert(err, IsNil)
1309 }
1310
1311 c.Logf("Waiting until secondary syncs")
1312 for {
1313 n, err := coll1.Count()
1314 c.Assert(err, IsNil)
1315 if n == N {
1316 c.Logf("Found all")
1317 break
1318 }
1319 }
1320
1321 session2, err := mgo.Dial("localhost:40011")
1322 c.Assert(err, IsNil)
1323 defer session2.Close()
1324
1325 session2.SetMode(mgo.Eventual, false)
1326
1327 coll2 := session2.DB("mydb").C("mycoll")
1328
1329 i := 0
1330 iter := coll2.Find(nil).Batch(10).Iter()
1331 var result struct{}
1332 for iter.Next(&result) {
1333 i++
1334 }
1335 c.Assert(iter.Close(), Equals, nil)
1336 c.Assert(i, Equals, N)
1337}
1338
1339func (s *S) TestCustomDialOld(c *C) {
1340 dials := make(chan bool, 16)
1341 dial := func(addr net.Addr) (net.Conn, error) {
1342 tcpaddr, ok := addr.(*net.TCPAddr)
1343 if !ok {
1344 return nil, fmt.Errorf("unexpected address type: %T", addr)
1345 }
1346 dials <- true
1347 return net.DialTCP("tcp", nil, tcpaddr)
1348 }
1349 info := mgo.DialInfo{
1350 Addrs: []string{"localhost:40012"},
1351 Dial: dial,
1352 }
1353
1354 // Use hostname here rather than IP, to make things trickier.
1355 session, err := mgo.DialWithInfo(&info)
1356 c.Assert(err, IsNil)
1357 defer session.Close()
1358
1359 const N = 3
1360 for i := 0; i < N; i++ {
1361 select {
1362 case <-dials:
1363 case <-time.After(5 * time.Second):
1364 c.Fatalf("expected %d dials, got %d", N, i)
1365 }
1366 }
1367 select {
1368 case <-dials:
1369 c.Fatalf("got more dials than expected")
1370 case <-time.After(100 * time.Millisecond):
1371 }
1372}
1373
1374func (s *S) TestCustomDialNew(c *C) {
1375 dials := make(chan bool, 16)
1376 dial := func(addr *mgo.ServerAddr) (net.Conn, error) {
1377 dials <- true
1378 if addr.TCPAddr().Port == 40012 {
1379 c.Check(addr.String(), Equals, "localhost:40012")
1380 }
1381 return net.DialTCP("tcp", nil, addr.TCPAddr())
1382 }
1383 info := mgo.DialInfo{
1384 Addrs: []string{"localhost:40012"},
1385 DialServer: dial,
1386 }
1387
1388 // Use hostname here rather than IP, to make things trickier.
1389 session, err := mgo.DialWithInfo(&info)
1390 c.Assert(err, IsNil)
1391 defer session.Close()
1392
1393 const N = 3
1394 for i := 0; i < N; i++ {
1395 select {
1396 case <-dials:
1397 case <-time.After(5 * time.Second):
1398 c.Fatalf("expected %d dials, got %d", N, i)
1399 }
1400 }
1401 select {
1402 case <-dials:
1403 c.Fatalf("got more dials than expected")
1404 case <-time.After(100 * time.Millisecond):
1405 }
1406}
1407
1408func (s *S) TestPrimaryShutdownOnAuthShard(c *C) {
1409 if *fast {
1410 c.Skip("-fast")
1411 }
1412
1413 // Dial the shard.
1414 session, err := mgo.Dial("localhost:40203")
1415 c.Assert(err, IsNil)
1416 defer session.Close()
1417
1418 // Login and insert something to make it more realistic.
1419 session.DB("admin").Login("root", "rapadura")
1420 coll := session.DB("mydb").C("mycoll")
1421 err = coll.Insert(bson.M{"n": 1})
1422 c.Assert(err, IsNil)
1423
1424 // Dial the replica set to figure the master out.
1425 rs, err := mgo.Dial("root:rapadura@localhost:40031")
1426 c.Assert(err, IsNil)
1427 defer rs.Close()
1428
1429 // With strong consistency, this will open a socket to the master.
1430 result := &struct{ Host string }{}
1431 err = rs.Run("serverStatus", result)
1432 c.Assert(err, IsNil)
1433
1434 // Kill the master.
1435 host := result.Host
1436 s.Stop(host)
1437
1438 // This must fail, since the connection was broken.
1439 err = rs.Run("serverStatus", result)
1440 c.Assert(err, Equals, io.EOF)
1441
1442 // This won't work because the master just died.
1443 err = coll.Insert(bson.M{"n": 2})
1444 c.Assert(err, NotNil)
1445
1446 // Refresh session and wait for re-election.
1447 session.Refresh()
1448 for i := 0; i < 60; i++ {
1449 err = coll.Insert(bson.M{"n": 3})
1450 if err == nil {
1451 break
1452 }
1453 c.Logf("Waiting for replica set to elect a new master. Last error: %v", err)
1454 time.Sleep(500 * time.Millisecond)
1455 }
1456 c.Assert(err, IsNil)
1457
1458 count, err := coll.Count()
1459 c.Assert(count > 1, Equals, true)
1460}
1461
1462func (s *S) TestNearestSecondary(c *C) {
1463 defer mgo.HackPingDelay(3 * time.Second)()
1464
1465 rs1a := "127.0.0.1:40011"
1466 rs1b := "127.0.0.1:40012"
1467 rs1c := "127.0.0.1:40013"
1468 s.Freeze(rs1b)
1469
1470 session, err := mgo.Dial(rs1a)
1471 c.Assert(err, IsNil)
1472 defer session.Close()
1473
1474 // Wait for the sync up to run through the first couple of servers.
1475 for len(session.LiveServers()) != 2 {
1476 c.Log("Waiting for two servers to be alive...")
1477 time.Sleep(100 * time.Millisecond)
1478 }
1479
1480 // Extra delay to ensure the third server gets penalized.
1481 time.Sleep(500 * time.Millisecond)
1482
1483 // Release third server.
1484 s.Thaw(rs1b)
1485
1486 // Wait for it to come up.
1487 for len(session.LiveServers()) != 3 {
1488 c.Log("Waiting for all servers to be alive...")
1489 time.Sleep(100 * time.Millisecond)
1490 }
1491
1492 session.SetMode(mgo.Monotonic, true)
1493 var result struct{ Host string }
1494
1495 // See which slave picks the line, several times to avoid chance.
1496 for i := 0; i < 10; i++ {
1497 session.Refresh()
1498 err = session.Run("serverStatus", &result)
1499 c.Assert(err, IsNil)
1500 c.Assert(hostPort(result.Host), Equals, hostPort(rs1c))
1501 }
1502
1503 if *fast {
1504 // Don't hold back for several seconds.
1505 return
1506 }
1507
1508 // Now hold the other server for long enough to penalize it.
1509 s.Freeze(rs1c)
1510 time.Sleep(5 * time.Second)
1511 s.Thaw(rs1c)
1512
1513 // Wait for the ping to be processed.
1514 time.Sleep(500 * time.Millisecond)
1515
1516 // Repeating the test should now pick the former server consistently.
1517 for i := 0; i < 10; i++ {
1518 session.Refresh()
1519 err = session.Run("serverStatus", &result)
1520 c.Assert(err, IsNil)
1521 c.Assert(hostPort(result.Host), Equals, hostPort(rs1b))
1522 }
1523}
1524
1525func (s *S) TestConnectCloseConcurrency(c *C) {
1526 restore := mgo.HackPingDelay(500 * time.Millisecond)
1527 defer restore()
1528 var wg sync.WaitGroup
1529 const n = 500
1530 wg.Add(n)
1531 for i := 0; i < n; i++ {
1532 go func() {
1533 defer wg.Done()
1534 session, err := mgo.Dial("localhost:40001")
1535 if err != nil {
1536 c.Fatal(err)
1537 }
1538 time.Sleep(1)
1539 session.Close()
1540 }()
1541 }
1542 wg.Wait()
1543}
1544
1545func (s *S) TestSelectServers(c *C) {
1546 if !s.versionAtLeast(2, 2) {
1547 c.Skip("read preferences introduced in 2.2")
1548 }
1549
1550 session, err := mgo.Dial("localhost:40011")
1551 c.Assert(err, IsNil)
1552 defer session.Close()
1553
1554 session.SetMode(mgo.Eventual, true)
1555
1556 var result struct{ Host string }
1557
1558 session.Refresh()
1559 session.SelectServers(bson.D{{"rs1", "b"}})
1560 err = session.Run("serverStatus", &result)
1561 c.Assert(err, IsNil)
1562 c.Assert(hostPort(result.Host), Equals, "40012")
1563
1564 session.Refresh()
1565 session.SelectServers(bson.D{{"rs1", "c"}})
1566 err = session.Run("serverStatus", &result)
1567 c.Assert(err, IsNil)
1568 c.Assert(hostPort(result.Host), Equals, "40013")
1569}
1570
1571func (s *S) TestSelectServersWithMongos(c *C) {
1572 if !s.versionAtLeast(2, 2) {
1573 c.Skip("read preferences introduced in 2.2")
1574 }
1575
1576 session, err := mgo.Dial("localhost:40021")
1577 c.Assert(err, IsNil)
1578 defer session.Close()
1579
1580 ssresult := &struct{ Host string }{}
1581 imresult := &struct{ IsMaster bool }{}
1582
1583 // Figure the master while still using the strong session.
1584 err = session.Run("serverStatus", ssresult)
1585 c.Assert(err, IsNil)
1586 err = session.Run("isMaster", imresult)
1587 c.Assert(err, IsNil)
1588 master := ssresult.Host
1589 c.Assert(imresult.IsMaster, Equals, true, Commentf("%s is not the master", master))
1590
1591 var slave1, slave2 string
1592 switch hostPort(master) {
1593 case "40021":
1594 slave1, slave2 = "b", "c"
1595 case "40022":
1596 slave1, slave2 = "a", "c"
1597 case "40023":
1598 slave1, slave2 = "a", "b"
1599 }
1600
1601 // Collect op counters for everyone.
1602 opc21a, err := getOpCounters("localhost:40021")
1603 c.Assert(err, IsNil)
1604 opc22a, err := getOpCounters("localhost:40022")
1605 c.Assert(err, IsNil)
1606 opc23a, err := getOpCounters("localhost:40023")
1607 c.Assert(err, IsNil)
1608
1609 // Do a SlaveOk query through MongoS
1610 mongos, err := mgo.Dial("localhost:40202")
1611 c.Assert(err, IsNil)
1612 defer mongos.Close()
1613
1614 mongos.SetMode(mgo.Monotonic, true)
1615
1616 mongos.Refresh()
1617 mongos.SelectServers(bson.D{{"rs2", slave1}})
1618 coll := mongos.DB("mydb").C("mycoll")
1619 result := &struct{}{}
1620 for i := 0; i != 5; i++ {
1621 err := coll.Find(nil).One(result)
1622 c.Assert(err, Equals, mgo.ErrNotFound)
1623 }
1624
1625 mongos.Refresh()
1626 mongos.SelectServers(bson.D{{"rs2", slave2}})
1627 coll = mongos.DB("mydb").C("mycoll")
1628 for i := 0; i != 7; i++ {
1629 err := coll.Find(nil).One(result)
1630 c.Assert(err, Equals, mgo.ErrNotFound)
1631 }
1632
1633 // Collect op counters for everyone again.
1634 opc21b, err := getOpCounters("localhost:40021")
1635 c.Assert(err, IsNil)
1636 opc22b, err := getOpCounters("localhost:40022")
1637 c.Assert(err, IsNil)
1638 opc23b, err := getOpCounters("localhost:40023")
1639 c.Assert(err, IsNil)
1640
1641 switch hostPort(master) {
1642 case "40021":
1643 c.Check(opc21b.Query-opc21a.Query, Equals, 0)
1644 c.Check(opc22b.Query-opc22a.Query, Equals, 5)
1645 c.Check(opc23b.Query-opc23a.Query, Equals, 7)
1646 case "40022":
1647 c.Check(opc21b.Query-opc21a.Query, Equals, 5)
1648 c.Check(opc22b.Query-opc22a.Query, Equals, 0)
1649 c.Check(opc23b.Query-opc23a.Query, Equals, 7)
1650 case "40023":
1651 c.Check(opc21b.Query-opc21a.Query, Equals, 5)
1652 c.Check(opc22b.Query-opc22a.Query, Equals, 7)
1653 c.Check(opc23b.Query-opc23a.Query, Equals, 0)
1654 default:
1655 c.Fatal("Uh?")
1656 }
1657}
vendor/gopkg.in/mgo.v2/doc.go+31−0
@@ -0,0 +1,31 @@
1// Package mgo offers a rich MongoDB driver for Go.
2//
3// Details about the mgo project (pronounced as "mango") are found
4// in its web page:
5//
6// http://labix.org/mgo
7//
8// Usage of the driver revolves around the concept of sessions. To
9// get started, obtain a session using the Dial function:
10//
11// session, err := mgo.Dial(url)
12//
13// This will establish one or more connections with the cluster of
14// servers defined by the url parameter. From then on, the cluster
15// may be queried with multiple consistency rules (see SetMode) and
16// documents retrieved with statements such as:
17//
18// c := session.DB(database).C(collection)
19// err := c.Find(query).One(&result)
20//
21// New sessions are typically created by calling session.Copy on the
22// initial session obtained at dial time. These new sessions will share
23// the same cluster information and connection cache, and may be easily
24// handed into other methods and functions for organizing logic.
25// Every session created must have its Close method called at the end
26// of its life time, so its resources may be put back in the pool or
27// collected, depending on the case.
28//
29// For more details, see the documentation for the types and methods.
30//
31package mgo
vendor/gopkg.in/mgo.v2/export_test.go+33−0
@@ -0,0 +1,33 @@
1package mgo
2
3import (
4 "time"
5)
6
7func HackPingDelay(newDelay time.Duration) (restore func()) {
8 globalMutex.Lock()
9 defer globalMutex.Unlock()
10
11 oldDelay := pingDelay
12 restore = func() {
13 globalMutex.Lock()
14 pingDelay = oldDelay
15 globalMutex.Unlock()
16 }
17 pingDelay = newDelay
18 return
19}
20
21func HackSyncSocketTimeout(newTimeout time.Duration) (restore func()) {
22 globalMutex.Lock()
23 defer globalMutex.Unlock()
24
25 oldTimeout := syncSocketTimeout
26 restore = func() {
27 globalMutex.Lock()
28 syncSocketTimeout = oldTimeout
29 globalMutex.Unlock()
30 }
31 syncSocketTimeout = newTimeout
32 return
33}
vendor/gopkg.in/mgo.v2/gridfs.go+755−0
@@ -0,0 +1,755 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29import (
30 "crypto/md5"
31 "encoding/hex"
32 "errors"
33 "hash"
34 "io"
35 "os"
36 "sync"
37 "time"
38
39 "gopkg.in/mgo.v2/bson"
40)
41
42type GridFS struct {
43 Files *Collection
44 Chunks *Collection
45}
46
47type gfsFileMode int
48
49const (
50 gfsClosed gfsFileMode = 0
51 gfsReading gfsFileMode = 1
52 gfsWriting gfsFileMode = 2
53)
54
55type GridFile struct {
56 m sync.Mutex
57 c sync.Cond
58 gfs *GridFS
59 mode gfsFileMode
60 err error
61
62 chunk int
63 offset int64
64
65 wpending int
66 wbuf []byte
67 wsum hash.Hash
68
69 rbuf []byte
70 rcache *gfsCachedChunk
71
72 doc gfsFile
73}
74
75type gfsFile struct {
76 Id interface{} "_id"
77 ChunkSize int "chunkSize"
78 UploadDate time.Time "uploadDate"
79 Length int64 ",minsize"
80 MD5 string
81 Filename string ",omitempty"
82 ContentType string "contentType,omitempty"
83 Metadata *bson.Raw ",omitempty"
84}
85
86type gfsChunk struct {
87 Id interface{} "_id"
88 FilesId interface{} "files_id"
89 N int
90 Data []byte
91}
92
93type gfsCachedChunk struct {
94 wait sync.Mutex
95 n int
96 data []byte
97 err error
98}
99
100func newGridFS(db *Database, prefix string) *GridFS {
101 return &GridFS{db.C(prefix + ".files"), db.C(prefix + ".chunks")}
102}
103
104func (gfs *GridFS) newFile() *GridFile {
105 file := &GridFile{gfs: gfs}
106 file.c.L = &file.m
107 //runtime.SetFinalizer(file, finalizeFile)
108 return file
109}
110
111func finalizeFile(file *GridFile) {
112 file.Close()
113}
114
115// Create creates a new file with the provided name in the GridFS. If the file
116// name already exists, a new version will be inserted with an up-to-date
117// uploadDate that will cause it to be atomically visible to the Open and
118// OpenId methods. If the file name is not important, an empty name may be
119// provided and the file Id used instead.
120//
121// It's important to Close files whether they are being written to
122// or read from, and to check the err result to ensure the operation
123// completed successfully.
124//
125// A simple example inserting a new file:
126//
127// func check(err error) {
128// if err != nil {
129// panic(err.String())
130// }
131// }
132// file, err := db.GridFS("fs").Create("myfile.txt")
133// check(err)
134// n, err := file.Write([]byte("Hello world!"))
135// check(err)
136// err = file.Close()
137// check(err)
138// fmt.Printf("%d bytes written\n", n)
139//
140// The io.Writer interface is implemented by *GridFile and may be used to
141// help on the file creation. For example:
142//
143// file, err := db.GridFS("fs").Create("myfile.txt")
144// check(err)
145// messages, err := os.Open("/var/log/messages")
146// check(err)
147// defer messages.Close()
148// err = io.Copy(file, messages)
149// check(err)
150// err = file.Close()
151// check(err)
152//
153func (gfs *GridFS) Create(name string) (file *GridFile, err error) {
154 file = gfs.newFile()
155 file.mode = gfsWriting
156 file.wsum = md5.New()
157 file.doc = gfsFile{Id: bson.NewObjectId(), ChunkSize: 255 * 1024, Filename: name}
158 return
159}
160
161// OpenId returns the file with the provided id, for reading.
162// If the file isn't found, err will be set to mgo.ErrNotFound.
163//
164// It's important to Close files whether they are being written to
165// or read from, and to check the err result to ensure the operation
166// completed successfully.
167//
168// The following example will print the first 8192 bytes from the file:
169//
170// func check(err error) {
171// if err != nil {
172// panic(err.String())
173// }
174// }
175// file, err := db.GridFS("fs").OpenId(objid)
176// check(err)
177// b := make([]byte, 8192)
178// n, err := file.Read(b)
179// check(err)
180// fmt.Println(string(b))
181// check(err)
182// err = file.Close()
183// check(err)
184// fmt.Printf("%d bytes read\n", n)
185//
186// The io.Reader interface is implemented by *GridFile and may be used to
187// deal with it. As an example, the following snippet will dump the whole
188// file into the standard output:
189//
190// file, err := db.GridFS("fs").OpenId(objid)
191// check(err)
192// err = io.Copy(os.Stdout, file)
193// check(err)
194// err = file.Close()
195// check(err)
196//
197func (gfs *GridFS) OpenId(id interface{}) (file *GridFile, err error) {
198 var doc gfsFile
199 err = gfs.Files.Find(bson.M{"_id": id}).One(&doc)
200 if err != nil {
201 return
202 }
203 file = gfs.newFile()
204 file.mode = gfsReading
205 file.doc = doc
206 return
207}
208
209// Open returns the most recently uploaded file with the provided
210// name, for reading. If the file isn't found, err will be set
211// to mgo.ErrNotFound.
212//
213// It's important to Close files whether they are being written to
214// or read from, and to check the err result to ensure the operation
215// completed successfully.
216//
217// The following example will print the first 8192 bytes from the file:
218//
219// file, err := db.GridFS("fs").Open("myfile.txt")
220// check(err)
221// b := make([]byte, 8192)
222// n, err := file.Read(b)
223// check(err)
224// fmt.Println(string(b))
225// check(err)
226// err = file.Close()
227// check(err)
228// fmt.Printf("%d bytes read\n", n)
229//
230// The io.Reader interface is implemented by *GridFile and may be used to
231// deal with it. As an example, the following snippet will dump the whole
232// file into the standard output:
233//
234// file, err := db.GridFS("fs").Open("myfile.txt")
235// check(err)
236// err = io.Copy(os.Stdout, file)
237// check(err)
238// err = file.Close()
239// check(err)
240//
241func (gfs *GridFS) Open(name string) (file *GridFile, err error) {
242 var doc gfsFile
243 err = gfs.Files.Find(bson.M{"filename": name}).Sort("-uploadDate").One(&doc)
244 if err != nil {
245 return
246 }
247 file = gfs.newFile()
248 file.mode = gfsReading
249 file.doc = doc
250 return
251}
252
253// OpenNext opens the next file from iter for reading, sets *file to it,
254// and returns true on the success case. If no more documents are available
255// on iter or an error occurred, *file is set to nil and the result is false.
256// Errors will be available via iter.Err().
257//
258// The iter parameter must be an iterator on the GridFS files collection.
259// Using the GridFS.Find method is an easy way to obtain such an iterator,
260// but any iterator on the collection will work.
261//
262// If the provided *file is non-nil, OpenNext will close it before attempting
263// to iterate to the next element. This means that in a loop one only
264// has to worry about closing files when breaking out of the loop early
265// (break, return, or panic).
266//
267// For example:
268//
269// gfs := db.GridFS("fs")
270// query := gfs.Find(nil).Sort("filename")
271// iter := query.Iter()
272// var f *mgo.GridFile
273// for gfs.OpenNext(iter, &f) {
274// fmt.Printf("Filename: %s\n", f.Name())
275// }
276// if iter.Close() != nil {
277// panic(iter.Close())
278// }
279//
280func (gfs *GridFS) OpenNext(iter *Iter, file **GridFile) bool {
281 if *file != nil {
282 // Ignoring the error here shouldn't be a big deal
283 // as we're reading the file and the loop iteration
284 // for this file is finished.
285 _ = (*file).Close()
286 }
287 var doc gfsFile
288 if !iter.Next(&doc) {
289 *file = nil
290 return false
291 }
292 f := gfs.newFile()
293 f.mode = gfsReading
294 f.doc = doc
295 *file = f
296 return true
297}
298
299// Find runs query on GridFS's files collection and returns
300// the resulting Query.
301//
302// This logic:
303//
304// gfs := db.GridFS("fs")
305// iter := gfs.Find(nil).Iter()
306//
307// Is equivalent to:
308//
309// files := db.C("fs" + ".files")
310// iter := files.Find(nil).Iter()
311//
312func (gfs *GridFS) Find(query interface{}) *Query {
313 return gfs.Files.Find(query)
314}
315
316// RemoveId deletes the file with the provided id from the GridFS.
317func (gfs *GridFS) RemoveId(id interface{}) error {
318 err := gfs.Files.Remove(bson.M{"_id": id})
319 if err != nil {
320 return err
321 }
322 _, err = gfs.Chunks.RemoveAll(bson.D{{"files_id", id}})
323 return err
324}
325
326type gfsDocId struct {
327 Id interface{} "_id"
328}
329
330// Remove deletes all files with the provided name from the GridFS.
331func (gfs *GridFS) Remove(name string) (err error) {
332 iter := gfs.Files.Find(bson.M{"filename": name}).Select(bson.M{"_id": 1}).Iter()
333 var doc gfsDocId
334 for iter.Next(&doc) {
335 if e := gfs.RemoveId(doc.Id); e != nil {
336 err = e
337 }
338 }
339 if err == nil {
340 err = iter.Close()
341 }
342 return err
343}
344
345func (file *GridFile) assertMode(mode gfsFileMode) {
346 switch file.mode {
347 case mode:
348 return
349 case gfsWriting:
350 panic("GridFile is open for writing")
351 case gfsReading:
352 panic("GridFile is open for reading")
353 case gfsClosed:
354 panic("GridFile is closed")
355 default:
356 panic("internal error: missing GridFile mode")
357 }
358}
359
360// SetChunkSize sets size of saved chunks. Once the file is written to, it
361// will be split in blocks of that size and each block saved into an
362// independent chunk document. The default chunk size is 256kb.
363//
364// It is a runtime error to call this function once the file has started
365// being written to.
366func (file *GridFile) SetChunkSize(bytes int) {
367 file.assertMode(gfsWriting)
368 debugf("GridFile %p: setting chunk size to %d", file, bytes)
369 file.m.Lock()
370 file.doc.ChunkSize = bytes
371 file.m.Unlock()
372}
373
374// Id returns the current file Id.
375func (file *GridFile) Id() interface{} {
376 return file.doc.Id
377}
378
379// SetId changes the current file Id.
380//
381// It is a runtime error to call this function once the file has started
382// being written to, or when the file is not open for writing.
383func (file *GridFile) SetId(id interface{}) {
384 file.assertMode(gfsWriting)
385 file.m.Lock()
386 file.doc.Id = id
387 file.m.Unlock()
388}
389
390// Name returns the optional file name. An empty string will be returned
391// in case it is unset.
392func (file *GridFile) Name() string {
393 return file.doc.Filename
394}
395
396// SetName changes the optional file name. An empty string may be used to
397// unset it.
398//
399// It is a runtime error to call this function when the file is not open
400// for writing.
401func (file *GridFile) SetName(name string) {
402 file.assertMode(gfsWriting)
403 file.m.Lock()
404 file.doc.Filename = name
405 file.m.Unlock()
406}
407
408// ContentType returns the optional file content type. An empty string will be
409// returned in case it is unset.
410func (file *GridFile) ContentType() string {
411 return file.doc.ContentType
412}
413
414// ContentType changes the optional file content type. An empty string may be
415// used to unset it.
416//
417// It is a runtime error to call this function when the file is not open
418// for writing.
419func (file *GridFile) SetContentType(ctype string) {
420 file.assertMode(gfsWriting)
421 file.m.Lock()
422 file.doc.ContentType = ctype
423 file.m.Unlock()
424}
425
426// GetMeta unmarshals the optional "metadata" field associated with the
427// file into the result parameter. The meaning of keys under that field
428// is user-defined. For example:
429//
430// result := struct{ INode int }{}
431// err = file.GetMeta(&result)
432// if err != nil {
433// panic(err.String())
434// }
435// fmt.Printf("inode: %d\n", result.INode)
436//
437func (file *GridFile) GetMeta(result interface{}) (err error) {
438 file.m.Lock()
439 if file.doc.Metadata != nil {
440 err = bson.Unmarshal(file.doc.Metadata.Data, result)
441 }
442 file.m.Unlock()
443 return
444}
445
446// SetMeta changes the optional "metadata" field associated with the
447// file. The meaning of keys under that field is user-defined.
448// For example:
449//
450// file.SetMeta(bson.M{"inode": inode})
451//
452// It is a runtime error to call this function when the file is not open
453// for writing.
454func (file *GridFile) SetMeta(metadata interface{}) {
455 file.assertMode(gfsWriting)
456 data, err := bson.Marshal(metadata)
457 file.m.Lock()
458 if err != nil && file.err == nil {
459 file.err = err
460 } else {
461 file.doc.Metadata = &bson.Raw{Data: data}
462 }
463 file.m.Unlock()
464}
465
466// Size returns the file size in bytes.
467func (file *GridFile) Size() (bytes int64) {
468 file.m.Lock()
469 bytes = file.doc.Length
470 file.m.Unlock()
471 return
472}
473
474// MD5 returns the file MD5 as a hex-encoded string.
475func (file *GridFile) MD5() (md5 string) {
476 return file.doc.MD5
477}
478
479// UploadDate returns the file upload time.
480func (file *GridFile) UploadDate() time.Time {
481 return file.doc.UploadDate
482}
483
484// SetUploadDate changes the file upload time.
485//
486// It is a runtime error to call this function when the file is not open
487// for writing.
488func (file *GridFile) SetUploadDate(t time.Time) {
489 file.assertMode(gfsWriting)
490 file.m.Lock()
491 file.doc.UploadDate = t
492 file.m.Unlock()
493}
494
495// Close flushes any pending changes in case the file is being written
496// to, waits for any background operations to finish, and closes the file.
497//
498// It's important to Close files whether they are being written to
499// or read from, and to check the err result to ensure the operation
500// completed successfully.
501func (file *GridFile) Close() (err error) {
502 file.m.Lock()
503 defer file.m.Unlock()
504 if file.mode == gfsWriting {
505 if len(file.wbuf) > 0 && file.err == nil {
506 file.insertChunk(file.wbuf)
507 file.wbuf = file.wbuf[0:0]
508 }
509 file.completeWrite()
510 } else if file.mode == gfsReading && file.rcache != nil {
511 file.rcache.wait.Lock()
512 file.rcache = nil
513 }
514 file.mode = gfsClosed
515 debugf("GridFile %p: closed", file)
516 return file.err
517}
518
519func (file *GridFile) completeWrite() {
520 for file.wpending > 0 {
521 debugf("GridFile %p: waiting for %d pending chunks to complete file write", file, file.wpending)
522 file.c.Wait()
523 }
524 if file.err == nil {
525 hexsum := hex.EncodeToString(file.wsum.Sum(nil))
526 if file.doc.UploadDate.IsZero() {
527 file.doc.UploadDate = bson.Now()
528 }
529 file.doc.MD5 = hexsum
530 file.err = file.gfs.Files.Insert(file.doc)
531 file.gfs.Chunks.EnsureIndexKey("files_id", "n")
532 }
533 if file.err != nil {
534 file.gfs.Chunks.RemoveAll(bson.D{{"files_id", file.doc.Id}})
535 }
536}
537
538// Abort cancels an in-progress write, preventing the file from being
539// automically created and ensuring previously written chunks are
540// removed when the file is closed.
541//
542// It is a runtime error to call Abort when the file was not opened
543// for writing.
544func (file *GridFile) Abort() {
545 if file.mode != gfsWriting {
546 panic("file.Abort must be called on file opened for writing")
547 }
548 file.err = errors.New("write aborted")
549}
550
551// Write writes the provided data to the file and returns the
552// number of bytes written and an error in case something
553// wrong happened.
554//
555// The file will internally cache the data so that all but the last
556// chunk sent to the database have the size defined by SetChunkSize.
557// This also means that errors may be deferred until a future call
558// to Write or Close.
559//
560// The parameters and behavior of this function turn the file
561// into an io.Writer.
562func (file *GridFile) Write(data []byte) (n int, err error) {
563 file.assertMode(gfsWriting)
564 file.m.Lock()
565 debugf("GridFile %p: writing %d bytes", file, len(data))
566 defer file.m.Unlock()
567
568 if file.err != nil {
569 return 0, file.err
570 }
571
572 n = len(data)
573 file.doc.Length += int64(n)
574 chunkSize := file.doc.ChunkSize
575
576 if len(file.wbuf)+len(data) < chunkSize {
577 file.wbuf = append(file.wbuf, data...)
578 return
579 }
580
581 // First, flush file.wbuf complementing with data.
582 if len(file.wbuf) > 0 {
583 missing := chunkSize - len(file.wbuf)
584 if missing > len(data) {
585 missing = len(data)
586 }
587 file.wbuf = append(file.wbuf, data[:missing]...)
588 data = data[missing:]
589 file.insertChunk(file.wbuf)
590 file.wbuf = file.wbuf[0:0]
591 }
592
593 // Then, flush all chunks from data without copying.
594 for len(data) > chunkSize {
595 size := chunkSize
596 if size > len(data) {
597 size = len(data)
598 }
599 file.insertChunk(data[:size])
600 data = data[size:]
601 }
602
603 // And append the rest for a future call.
604 file.wbuf = append(file.wbuf, data...)
605
606 return n, file.err
607}
608
609func (file *GridFile) insertChunk(data []byte) {
610 n := file.chunk
611 file.chunk++
612 debugf("GridFile %p: adding to checksum: %q", file, string(data))
613 file.wsum.Write(data)
614
615 for file.doc.ChunkSize*file.wpending >= 1024*1024 {
616 // Hold on.. we got a MB pending.
617 file.c.Wait()
618 if file.err != nil {
619 return
620 }
621 }
622
623 file.wpending++
624
625 debugf("GridFile %p: inserting chunk %d with %d bytes", file, n, len(data))
626
627 // We may not own the memory of data, so rather than
628 // simply copying it, we'll marshal the document ahead of time.
629 data, err := bson.Marshal(gfsChunk{bson.NewObjectId(), file.doc.Id, n, data})
630 if err != nil {
631 file.err = err
632 return
633 }
634
635 go func() {
636 err := file.gfs.Chunks.Insert(bson.Raw{Data: data})
637 file.m.Lock()
638 file.wpending--
639 if err != nil && file.err == nil {
640 file.err = err
641 }
642 file.c.Broadcast()
643 file.m.Unlock()
644 }()
645}
646
647// Seek sets the offset for the next Read or Write on file to
648// offset, interpreted according to whence: 0 means relative to
649// the origin of the file, 1 means relative to the current offset,
650// and 2 means relative to the end. It returns the new offset and
651// an error, if any.
652func (file *GridFile) Seek(offset int64, whence int) (pos int64, err error) {
653 file.m.Lock()
654 debugf("GridFile %p: seeking for %s (whence=%d)", file, offset, whence)
655 defer file.m.Unlock()
656 switch whence {
657 case os.SEEK_SET:
658 case os.SEEK_CUR:
659 offset += file.offset
660 case os.SEEK_END:
661 offset += file.doc.Length
662 default:
663 panic("unsupported whence value")
664 }
665 if offset > file.doc.Length {
666 return file.offset, errors.New("seek past end of file")
667 }
668 if offset == file.doc.Length {
669 // If we're seeking to the end of the file,
670 // no need to read anything. This enables
671 // a client to find the size of the file using only the
672 // io.ReadSeeker interface with low overhead.
673 file.offset = offset
674 return file.offset, nil
675 }
676 chunk := int(offset / int64(file.doc.ChunkSize))
677 if chunk+1 == file.chunk && offset >= file.offset {
678 file.rbuf = file.rbuf[int(offset-file.offset):]
679 file.offset = offset
680 return file.offset, nil
681 }
682 file.offset = offset
683 file.chunk = chunk
684 file.rbuf = nil
685 file.rbuf, err = file.getChunk()
686 if err == nil {
687 file.rbuf = file.rbuf[int(file.offset-int64(chunk)*int64(file.doc.ChunkSize)):]
688 }
689 return file.offset, err
690}
691
692// Read reads into b the next available data from the file and
693// returns the number of bytes written and an error in case
694// something wrong happened. At the end of the file, n will
695// be zero and err will be set to os.EOF.
696//
697// The parameters and behavior of this function turn the file
698// into an io.Reader.
699func (file *GridFile) Read(b []byte) (n int, err error) {
700 file.assertMode(gfsReading)
701 file.m.Lock()
702 debugf("GridFile %p: reading at offset %d into buffer of length %d", file, file.offset, len(b))
703 defer file.m.Unlock()
704 if file.offset == file.doc.Length {
705 return 0, io.EOF
706 }
707 for err == nil {
708 i := copy(b, file.rbuf)
709 n += i
710 file.offset += int64(i)
711 file.rbuf = file.rbuf[i:]
712 if i == len(b) || file.offset == file.doc.Length {
713 break
714 }
715 b = b[i:]
716 file.rbuf, err = file.getChunk()
717 }
718 return n, err
719}
720
721func (file *GridFile) getChunk() (data []byte, err error) {
722 cache := file.rcache
723 file.rcache = nil
724 if cache != nil && cache.n == file.chunk {
725 debugf("GridFile %p: Getting chunk %d from cache", file, file.chunk)
726 cache.wait.Lock()
727 data, err = cache.data, cache.err
728 } else {
729 debugf("GridFile %p: Fetching chunk %d", file, file.chunk)
730 var doc gfsChunk
731 err = file.gfs.Chunks.Find(bson.D{{"files_id", file.doc.Id}, {"n", file.chunk}}).One(&doc)
732 data = doc.Data
733 }
734 file.chunk++
735 if int64(file.chunk)*int64(file.doc.ChunkSize) < file.doc.Length {
736 // Read the next one in background.
737 cache = &gfsCachedChunk{n: file.chunk}
738 cache.wait.Lock()
739 debugf("GridFile %p: Scheduling chunk %d for background caching", file, file.chunk)
740 // Clone the session to avoid having it closed in between.
741 chunks := file.gfs.Chunks
742 session := chunks.Database.Session.Clone()
743 go func(id interface{}, n int) {
744 defer session.Close()
745 chunks = chunks.With(session)
746 var doc gfsChunk
747 cache.err = chunks.Find(bson.D{{"files_id", id}, {"n", n}}).One(&doc)
748 cache.data = doc.Data
749 cache.wait.Unlock()
750 }(file.doc.Id, file.chunk)
751 file.rcache = cache
752 }
753 debugf("Returning err: %#v", err)
754 return
755}
vendor/gopkg.in/mgo.v2/gridfs_test.go+708−0
@@ -0,0 +1,708 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo_test
28
29import (
30 "io"
31 "os"
32 "time"
33
34 . "gopkg.in/check.v1"
35 "gopkg.in/mgo.v2"
36 "gopkg.in/mgo.v2/bson"
37)
38
39func (s *S) TestGridFSCreate(c *C) {
40 session, err := mgo.Dial("localhost:40011")
41 c.Assert(err, IsNil)
42 defer session.Close()
43
44 db := session.DB("mydb")
45
46 before := bson.Now()
47
48 gfs := db.GridFS("fs")
49 file, err := gfs.Create("")
50 c.Assert(err, IsNil)
51
52 n, err := file.Write([]byte("some data"))
53 c.Assert(err, IsNil)
54 c.Assert(n, Equals, 9)
55
56 err = file.Close()
57 c.Assert(err, IsNil)
58
59 after := bson.Now()
60
61 // Check the file information.
62 result := M{}
63 err = db.C("fs.files").Find(nil).One(result)
64 c.Assert(err, IsNil)
65
66 fileId, ok := result["_id"].(bson.ObjectId)
67 c.Assert(ok, Equals, true)
68 c.Assert(fileId.Valid(), Equals, true)
69 result["_id"] = "<id>"
70
71 ud, ok := result["uploadDate"].(time.Time)
72 c.Assert(ok, Equals, true)
73 c.Assert(ud.After(before) && ud.Before(after), Equals, true)
74 result["uploadDate"] = "<timestamp>"
75
76 expected := M{
77 "_id": "<id>",
78 "length": 9,
79 "chunkSize": 255 * 1024,
80 "uploadDate": "<timestamp>",
81 "md5": "1e50210a0202497fb79bc38b6ade6c34",
82 }
83 c.Assert(result, DeepEquals, expected)
84
85 // Check the chunk.
86 result = M{}
87 err = db.C("fs.chunks").Find(nil).One(result)
88 c.Assert(err, IsNil)
89
90 chunkId, ok := result["_id"].(bson.ObjectId)
91 c.Assert(ok, Equals, true)
92 c.Assert(chunkId.Valid(), Equals, true)
93 result["_id"] = "<id>"
94
95 expected = M{
96 "_id": "<id>",
97 "files_id": fileId,
98 "n": 0,
99 "data": []byte("some data"),
100 }
101 c.Assert(result, DeepEquals, expected)
102
103 // Check that an index was created.
104 indexes, err := db.C("fs.chunks").Indexes()
105 c.Assert(err, IsNil)
106 c.Assert(len(indexes), Equals, 2)
107 c.Assert(indexes[1].Key, DeepEquals, []string{"files_id", "n"})
108}
109
110func (s *S) TestGridFSFileDetails(c *C) {
111 session, err := mgo.Dial("localhost:40011")
112 c.Assert(err, IsNil)
113 defer session.Close()
114
115 db := session.DB("mydb")
116
117 gfs := db.GridFS("fs")
118
119 file, err := gfs.Create("myfile1.txt")
120 c.Assert(err, IsNil)
121
122 n, err := file.Write([]byte("some"))
123 c.Assert(err, IsNil)
124 c.Assert(n, Equals, 4)
125
126 c.Assert(file.Size(), Equals, int64(4))
127
128 n, err = file.Write([]byte(" data"))
129 c.Assert(err, IsNil)
130 c.Assert(n, Equals, 5)
131
132 c.Assert(file.Size(), Equals, int64(9))
133
134 id, _ := file.Id().(bson.ObjectId)
135 c.Assert(id.Valid(), Equals, true)
136 c.Assert(file.Name(), Equals, "myfile1.txt")
137 c.Assert(file.ContentType(), Equals, "")
138
139 var info interface{}
140 err = file.GetMeta(&info)
141 c.Assert(err, IsNil)
142 c.Assert(info, IsNil)
143
144 file.SetId("myid")
145 file.SetName("myfile2.txt")
146 file.SetContentType("text/plain")
147 file.SetMeta(M{"any": "thing"})
148
149 c.Assert(file.Id(), Equals, "myid")
150 c.Assert(file.Name(), Equals, "myfile2.txt")
151 c.Assert(file.ContentType(), Equals, "text/plain")
152
153 err = file.GetMeta(&info)
154 c.Assert(err, IsNil)
155 c.Assert(info, DeepEquals, bson.M{"any": "thing"})
156
157 err = file.Close()
158 c.Assert(err, IsNil)
159
160 c.Assert(file.MD5(), Equals, "1e50210a0202497fb79bc38b6ade6c34")
161
162 ud := file.UploadDate()
163 now := time.Now()
164 c.Assert(ud.Before(now), Equals, true)
165 c.Assert(ud.After(now.Add(-3*time.Second)), Equals, true)
166
167 result := M{}
168 err = db.C("fs.files").Find(nil).One(result)
169 c.Assert(err, IsNil)
170
171 result["uploadDate"] = "<timestamp>"
172
173 expected := M{
174 "_id": "myid",
175 "length": 9,
176 "chunkSize": 255 * 1024,
177 "uploadDate": "<timestamp>",
178 "md5": "1e50210a0202497fb79bc38b6ade6c34",
179 "filename": "myfile2.txt",
180 "contentType": "text/plain",
181 "metadata": M{"any": "thing"},
182 }
183 c.Assert(result, DeepEquals, expected)
184}
185
186func (s *S) TestGridFSSetUploadDate(c *C) {
187 session, err := mgo.Dial("localhost:40011")
188 c.Assert(err, IsNil)
189 defer session.Close()
190
191 db := session.DB("mydb")
192
193 gfs := db.GridFS("fs")
194 file, err := gfs.Create("")
195 c.Assert(err, IsNil)
196
197 t := time.Date(2014, 1, 1, 1, 1, 1, 0, time.Local)
198 file.SetUploadDate(t)
199
200 err = file.Close()
201 c.Assert(err, IsNil)
202
203 // Check the file information.
204 result := M{}
205 err = db.C("fs.files").Find(nil).One(result)
206 c.Assert(err, IsNil)
207
208 ud := result["uploadDate"].(time.Time)
209 if !ud.Equal(t) {
210 c.Fatalf("want upload date %s, got %s", t, ud)
211 }
212}
213
214func (s *S) TestGridFSCreateWithChunking(c *C) {
215 session, err := mgo.Dial("localhost:40011")
216 c.Assert(err, IsNil)
217 defer session.Close()
218
219 db := session.DB("mydb")
220
221 gfs := db.GridFS("fs")
222
223 file, err := gfs.Create("")
224 c.Assert(err, IsNil)
225
226 file.SetChunkSize(5)
227
228 // Smaller than the chunk size.
229 n, err := file.Write([]byte("abc"))
230 c.Assert(err, IsNil)
231 c.Assert(n, Equals, 3)
232
233 // Boundary in the middle.
234 n, err = file.Write([]byte("defg"))
235 c.Assert(err, IsNil)
236 c.Assert(n, Equals, 4)
237
238 // Boundary at the end.
239 n, err = file.Write([]byte("hij"))
240 c.Assert(err, IsNil)
241 c.Assert(n, Equals, 3)
242
243 // Larger than the chunk size, with 3 chunks.
244 n, err = file.Write([]byte("klmnopqrstuv"))
245 c.Assert(err, IsNil)
246 c.Assert(n, Equals, 12)
247
248 err = file.Close()
249 c.Assert(err, IsNil)
250
251 // Check the file information.
252 result := M{}
253 err = db.C("fs.files").Find(nil).One(result)
254 c.Assert(err, IsNil)
255
256 fileId, _ := result["_id"].(bson.ObjectId)
257 c.Assert(fileId.Valid(), Equals, true)
258 result["_id"] = "<id>"
259 result["uploadDate"] = "<timestamp>"
260
261 expected := M{
262 "_id": "<id>",
263 "length": 22,
264 "chunkSize": 5,
265 "uploadDate": "<timestamp>",
266 "md5": "44a66044834cbe55040089cabfc102d5",
267 }
268 c.Assert(result, DeepEquals, expected)
269
270 // Check the chunks.
271 iter := db.C("fs.chunks").Find(nil).Sort("n").Iter()
272 dataChunks := []string{"abcde", "fghij", "klmno", "pqrst", "uv"}
273 for i := 0; ; i++ {
274 result = M{}
275 if !iter.Next(result) {
276 if i != 5 {
277 c.Fatalf("Expected 5 chunks, got %d", i)
278 }
279 break
280 }
281 c.Assert(iter.Close(), IsNil)
282
283 result["_id"] = "<id>"
284
285 expected = M{
286 "_id": "<id>",
287 "files_id": fileId,
288 "n": i,
289 "data": []byte(dataChunks[i]),
290 }
291 c.Assert(result, DeepEquals, expected)
292 }
293}
294
295func (s *S) TestGridFSAbort(c *C) {
296 session, err := mgo.Dial("localhost:40011")
297 c.Assert(err, IsNil)
298 defer session.Close()
299
300 db := session.DB("mydb")
301
302 gfs := db.GridFS("fs")
303 file, err := gfs.Create("")
304 c.Assert(err, IsNil)
305
306 file.SetChunkSize(5)
307
308 n, err := file.Write([]byte("some data"))
309 c.Assert(err, IsNil)
310 c.Assert(n, Equals, 9)
311
312 var count int
313 for i := 0; i < 10; i++ {
314 count, err = db.C("fs.chunks").Count()
315 if count > 0 || err != nil {
316 break
317 }
318 }
319 c.Assert(err, IsNil)
320 c.Assert(count, Equals, 1)
321
322 file.Abort()
323
324 err = file.Close()
325 c.Assert(err, ErrorMatches, "write aborted")
326
327 count, err = db.C("fs.chunks").Count()
328 c.Assert(err, IsNil)
329 c.Assert(count, Equals, 0)
330}
331
332func (s *S) TestGridFSCloseConflict(c *C) {
333 session, err := mgo.Dial("localhost:40011")
334 c.Assert(err, IsNil)
335 defer session.Close()
336
337 db := session.DB("mydb")
338
339 db.C("fs.files").EnsureIndex(mgo.Index{Key: []string{"filename"}, Unique: true})
340
341 // For a closing-time conflict
342 err = db.C("fs.files").Insert(M{"filename": "foo.txt"})
343 c.Assert(err, IsNil)
344
345 gfs := db.GridFS("fs")
346 file, err := gfs.Create("foo.txt")
347 c.Assert(err, IsNil)
348
349 _, err = file.Write([]byte("some data"))
350 c.Assert(err, IsNil)
351
352 err = file.Close()
353 c.Assert(mgo.IsDup(err), Equals, true)
354
355 count, err := db.C("fs.chunks").Count()
356 c.Assert(err, IsNil)
357 c.Assert(count, Equals, 0)
358}
359
360func (s *S) TestGridFSOpenNotFound(c *C) {
361 session, err := mgo.Dial("localhost:40011")
362 c.Assert(err, IsNil)
363 defer session.Close()
364
365 db := session.DB("mydb")
366
367 gfs := db.GridFS("fs")
368 file, err := gfs.OpenId("non-existent")
369 c.Assert(err == mgo.ErrNotFound, Equals, true)
370 c.Assert(file, IsNil)
371
372 file, err = gfs.Open("non-existent")
373 c.Assert(err == mgo.ErrNotFound, Equals, true)
374 c.Assert(file, IsNil)
375}
376
377func (s *S) TestGridFSReadAll(c *C) {
378 session, err := mgo.Dial("localhost:40011")
379 c.Assert(err, IsNil)
380 defer session.Close()
381
382 db := session.DB("mydb")
383
384 gfs := db.GridFS("fs")
385 file, err := gfs.Create("")
386 c.Assert(err, IsNil)
387 id := file.Id()
388
389 file.SetChunkSize(5)
390
391 n, err := file.Write([]byte("abcdefghijklmnopqrstuv"))
392 c.Assert(err, IsNil)
393 c.Assert(n, Equals, 22)
394
395 err = file.Close()
396 c.Assert(err, IsNil)
397
398 file, err = gfs.OpenId(id)
399 c.Assert(err, IsNil)
400
401 b := make([]byte, 30)
402 n, err = file.Read(b)
403 c.Assert(n, Equals, 22)
404 c.Assert(err, IsNil)
405
406 n, err = file.Read(b)
407 c.Assert(n, Equals, 0)
408 c.Assert(err == io.EOF, Equals, true)
409
410 err = file.Close()
411 c.Assert(err, IsNil)
412}
413
414func (s *S) TestGridFSReadChunking(c *C) {
415 session, err := mgo.Dial("localhost:40011")
416 c.Assert(err, IsNil)
417 defer session.Close()
418
419 db := session.DB("mydb")
420
421 gfs := db.GridFS("fs")
422
423 file, err := gfs.Create("")
424 c.Assert(err, IsNil)
425
426 id := file.Id()
427
428 file.SetChunkSize(5)
429
430 n, err := file.Write([]byte("abcdefghijklmnopqrstuv"))
431 c.Assert(err, IsNil)
432 c.Assert(n, Equals, 22)
433
434 err = file.Close()
435 c.Assert(err, IsNil)
436
437 file, err = gfs.OpenId(id)
438 c.Assert(err, IsNil)
439
440 b := make([]byte, 30)
441
442 // Smaller than the chunk size.
443 n, err = file.Read(b[:3])
444 c.Assert(err, IsNil)
445 c.Assert(n, Equals, 3)
446 c.Assert(b[:3], DeepEquals, []byte("abc"))
447
448 // Boundary in the middle.
449 n, err = file.Read(b[:4])
450 c.Assert(err, IsNil)
451 c.Assert(n, Equals, 4)
452 c.Assert(b[:4], DeepEquals, []byte("defg"))
453
454 // Boundary at the end.
455 n, err = file.Read(b[:3])
456 c.Assert(err, IsNil)
457 c.Assert(n, Equals, 3)
458 c.Assert(b[:3], DeepEquals, []byte("hij"))
459
460 // Larger than the chunk size, with 3 chunks.
461 n, err = file.Read(b)
462 c.Assert(err, IsNil)
463 c.Assert(n, Equals, 12)
464 c.Assert(b[:12], DeepEquals, []byte("klmnopqrstuv"))
465
466 n, err = file.Read(b)
467 c.Assert(n, Equals, 0)
468 c.Assert(err == io.EOF, Equals, true)
469
470 err = file.Close()
471 c.Assert(err, IsNil)
472}
473
474func (s *S) TestGridFSOpen(c *C) {
475 session, err := mgo.Dial("localhost:40011")
476 c.Assert(err, IsNil)
477 defer session.Close()
478
479 db := session.DB("mydb")
480
481 gfs := db.GridFS("fs")
482
483 file, err := gfs.Create("myfile.txt")
484 c.Assert(err, IsNil)
485 file.Write([]byte{'1'})
486 file.Close()
487
488 file, err = gfs.Create("myfile.txt")
489 c.Assert(err, IsNil)
490 file.Write([]byte{'2'})
491 file.Close()
492
493 file, err = gfs.Open("myfile.txt")
494 c.Assert(err, IsNil)
495 defer file.Close()
496
497 var b [1]byte
498
499 _, err = file.Read(b[:])
500 c.Assert(err, IsNil)
501 c.Assert(string(b[:]), Equals, "2")
502}
503
504func (s *S) TestGridFSSeek(c *C) {
505 session, err := mgo.Dial("localhost:40011")
506 c.Assert(err, IsNil)
507 defer session.Close()
508
509 db := session.DB("mydb")
510
511 gfs := db.GridFS("fs")
512 file, err := gfs.Create("")
513 c.Assert(err, IsNil)
514 id := file.Id()
515
516 file.SetChunkSize(5)
517
518 n, err := file.Write([]byte("abcdefghijklmnopqrstuv"))
519 c.Assert(err, IsNil)
520 c.Assert(n, Equals, 22)
521
522 err = file.Close()
523 c.Assert(err, IsNil)
524
525 b := make([]byte, 5)
526
527 file, err = gfs.OpenId(id)
528 c.Assert(err, IsNil)
529
530 o, err := file.Seek(3, os.SEEK_SET)
531 c.Assert(err, IsNil)
532 c.Assert(o, Equals, int64(3))
533 _, err = file.Read(b)
534 c.Assert(err, IsNil)
535 c.Assert(b, DeepEquals, []byte("defgh"))
536
537 o, err = file.Seek(5, os.SEEK_CUR)
538 c.Assert(err, IsNil)
539 c.Assert(o, Equals, int64(13))
540 _, err = file.Read(b)
541 c.Assert(err, IsNil)
542 c.Assert(b, DeepEquals, []byte("nopqr"))
543
544 o, err = file.Seek(0, os.SEEK_END)
545 c.Assert(err, IsNil)
546 c.Assert(o, Equals, int64(22))
547 n, err = file.Read(b)
548 c.Assert(err, Equals, io.EOF)
549 c.Assert(n, Equals, 0)
550
551 o, err = file.Seek(-10, os.SEEK_END)
552 c.Assert(err, IsNil)
553 c.Assert(o, Equals, int64(12))
554 _, err = file.Read(b)
555 c.Assert(err, IsNil)
556 c.Assert(b, DeepEquals, []byte("mnopq"))
557
558 o, err = file.Seek(8, os.SEEK_SET)
559 c.Assert(err, IsNil)
560 c.Assert(o, Equals, int64(8))
561 _, err = file.Read(b)
562 c.Assert(err, IsNil)
563 c.Assert(b, DeepEquals, []byte("ijklm"))
564
565 // Trivial seek forward within same chunk. Already
566 // got the data, shouldn't touch the database.
567 sent := mgo.GetStats().SentOps
568 o, err = file.Seek(1, os.SEEK_CUR)
569 c.Assert(err, IsNil)
570 c.Assert(o, Equals, int64(14))
571 c.Assert(mgo.GetStats().SentOps, Equals, sent)
572 _, err = file.Read(b)
573 c.Assert(err, IsNil)
574 c.Assert(b, DeepEquals, []byte("opqrs"))
575
576 // Try seeking past end of file.
577 file.Seek(3, os.SEEK_SET)
578 o, err = file.Seek(23, os.SEEK_SET)
579 c.Assert(err, ErrorMatches, "seek past end of file")
580 c.Assert(o, Equals, int64(3))
581}
582
583func (s *S) TestGridFSRemoveId(c *C) {
584 session, err := mgo.Dial("localhost:40011")
585 c.Assert(err, IsNil)
586 defer session.Close()
587
588 db := session.DB("mydb")
589
590 gfs := db.GridFS("fs")
591
592 file, err := gfs.Create("myfile.txt")
593 c.Assert(err, IsNil)
594 file.Write([]byte{'1'})
595 file.Close()
596
597 file, err = gfs.Create("myfile.txt")
598 c.Assert(err, IsNil)
599 file.Write([]byte{'2'})
600 id := file.Id()
601 file.Close()
602
603 err = gfs.RemoveId(id)
604 c.Assert(err, IsNil)
605
606 file, err = gfs.Open("myfile.txt")
607 c.Assert(err, IsNil)
608 defer file.Close()
609
610 var b [1]byte
611
612 _, err = file.Read(b[:])
613 c.Assert(err, IsNil)
614 c.Assert(string(b[:]), Equals, "1")
615
616 n, err := db.C("fs.chunks").Find(M{"files_id": id}).Count()
617 c.Assert(err, IsNil)
618 c.Assert(n, Equals, 0)
619}
620
621func (s *S) TestGridFSRemove(c *C) {
622 session, err := mgo.Dial("localhost:40011")
623 c.Assert(err, IsNil)
624 defer session.Close()
625
626 db := session.DB("mydb")
627
628 gfs := db.GridFS("fs")
629
630 file, err := gfs.Create("myfile.txt")
631 c.Assert(err, IsNil)
632 file.Write([]byte{'1'})
633 file.Close()
634
635 file, err = gfs.Create("myfile.txt")
636 c.Assert(err, IsNil)
637 file.Write([]byte{'2'})
638 file.Close()
639
640 err = gfs.Remove("myfile.txt")
641 c.Assert(err, IsNil)
642
643 _, err = gfs.Open("myfile.txt")
644 c.Assert(err == mgo.ErrNotFound, Equals, true)
645
646 n, err := db.C("fs.chunks").Find(nil).Count()
647 c.Assert(err, IsNil)
648 c.Assert(n, Equals, 0)
649}
650
651func (s *S) TestGridFSOpenNext(c *C) {
652 session, err := mgo.Dial("localhost:40011")
653 c.Assert(err, IsNil)
654 defer session.Close()
655
656 db := session.DB("mydb")
657
658 gfs := db.GridFS("fs")
659
660 file, err := gfs.Create("myfile1.txt")
661 c.Assert(err, IsNil)
662 file.Write([]byte{'1'})
663 file.Close()
664
665 file, err = gfs.Create("myfile2.txt")
666 c.Assert(err, IsNil)
667 file.Write([]byte{'2'})
668 file.Close()
669
670 var f *mgo.GridFile
671 var b [1]byte
672
673 iter := gfs.Find(nil).Sort("-filename").Iter()
674
675 ok := gfs.OpenNext(iter, &f)
676 c.Assert(ok, Equals, true)
677 c.Check(f.Name(), Equals, "myfile2.txt")
678
679 _, err = f.Read(b[:])
680 c.Assert(err, IsNil)
681 c.Assert(string(b[:]), Equals, "2")
682
683 ok = gfs.OpenNext(iter, &f)
684 c.Assert(ok, Equals, true)
685 c.Check(f.Name(), Equals, "myfile1.txt")
686
687 _, err = f.Read(b[:])
688 c.Assert(err, IsNil)
689 c.Assert(string(b[:]), Equals, "1")
690
691 ok = gfs.OpenNext(iter, &f)
692 c.Assert(ok, Equals, false)
693 c.Assert(iter.Close(), IsNil)
694 c.Assert(f, IsNil)
695
696 // Do it again with a more restrictive query to make sure
697 // it's actually taken into account.
698 iter = gfs.Find(bson.M{"filename": "myfile1.txt"}).Iter()
699
700 ok = gfs.OpenNext(iter, &f)
701 c.Assert(ok, Equals, true)
702 c.Check(f.Name(), Equals, "myfile1.txt")
703
704 ok = gfs.OpenNext(iter, &f)
705 c.Assert(ok, Equals, false)
706 c.Assert(iter.Close(), IsNil)
707 c.Assert(f, IsNil)
708}
vendor/gopkg.in/mgo.v2/internal/sasl/sasl.c+77−0
@@ -0,0 +1,77 @@
1// +build !windows
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <string.h>
6#include <sasl/sasl.h>
7
8static int mgo_sasl_simple(void *context, int id, const char **result, unsigned int *len)
9{
10 if (!result) {
11 return SASL_BADPARAM;
12 }
13 switch (id) {
14 case SASL_CB_USER:
15 *result = (char *)context;
16 break;
17 case SASL_CB_AUTHNAME:
18 *result = (char *)context;
19 break;
20 case SASL_CB_LANGUAGE:
21 *result = NULL;
22 break;
23 default:
24 return SASL_BADPARAM;
25 }
26 if (len) {
27 *len = *result ? strlen(*result) : 0;
28 }
29 return SASL_OK;
30}
31
32typedef int (*callback)(void);
33
34static int mgo_sasl_secret(sasl_conn_t *conn, void *context, int id, sasl_secret_t **result)
35{
36 if (!conn || !result || id != SASL_CB_PASS) {
37 return SASL_BADPARAM;
38 }
39 *result = (sasl_secret_t *)context;
40 return SASL_OK;
41}
42
43sasl_callback_t *mgo_sasl_callbacks(const char *username, const char *password)
44{
45 sasl_callback_t *cb = malloc(4 * sizeof(sasl_callback_t));
46 int n = 0;
47
48 size_t len = strlen(password);
49 sasl_secret_t *secret = (sasl_secret_t*)malloc(sizeof(sasl_secret_t) + len);
50 if (!secret) {
51 free(cb);
52 return NULL;
53 }
54 strcpy((char *)secret->data, password);
55 secret->len = len;
56
57 cb[n].id = SASL_CB_PASS;
58 cb[n].proc = (callback)&mgo_sasl_secret;
59 cb[n].context = secret;
60 n++;
61
62 cb[n].id = SASL_CB_USER;
63 cb[n].proc = (callback)&mgo_sasl_simple;
64 cb[n].context = (char*)username;
65 n++;
66
67 cb[n].id = SASL_CB_AUTHNAME;
68 cb[n].proc = (callback)&mgo_sasl_simple;
69 cb[n].context = (char*)username;
70 n++;
71
72 cb[n].id = SASL_CB_LIST_END;
73 cb[n].proc = NULL;
74 cb[n].context = NULL;
75
76 return cb;
77}
vendor/gopkg.in/mgo.v2/internal/sasl/sasl.go+138−0
@@ -0,0 +1,138 @@
1// Package sasl is an implementation detail of the mgo package.
2//
3// This package is not meant to be used by itself.
4//
5
6// +build !windows
7
8package sasl
9
10// #cgo LDFLAGS: -lsasl2
11//
12// struct sasl_conn {};
13//
14// #include <stdlib.h>
15// #include <sasl/sasl.h>
16//
17// sasl_callback_t *mgo_sasl_callbacks(const char *username, const char *password);
18//
19import "C"
20
21import (
22 "fmt"
23 "strings"
24 "sync"
25 "unsafe"
26)
27
28type saslStepper interface {
29 Step(serverData []byte) (clientData []byte, done bool, err error)
30 Close()
31}
32
33type saslSession struct {
34 conn *C.sasl_conn_t
35 step int
36 mech string
37
38 cstrings []*C.char
39 callbacks *C.sasl_callback_t
40}
41
42var initError error
43var initOnce sync.Once
44
45func initSASL() {
46 rc := C.sasl_client_init(nil)
47 if rc != C.SASL_OK {
48 initError = saslError(rc, nil, "cannot initialize SASL library")
49 }
50}
51
52func New(username, password, mechanism, service, host string) (saslStepper, error) {
53 initOnce.Do(initSASL)
54 if initError != nil {
55 return nil, initError
56 }
57
58 ss := &saslSession{mech: mechanism}
59 if service == "" {
60 service = "mongodb"
61 }
62 if i := strings.Index(host, ":"); i >= 0 {
63 host = host[:i]
64 }
65 ss.callbacks = C.mgo_sasl_callbacks(ss.cstr(username), ss.cstr(password))
66 rc := C.sasl_client_new(ss.cstr(service), ss.cstr(host), nil, nil, ss.callbacks, 0, &ss.conn)
67 if rc != C.SASL_OK {
68 ss.Close()
69 return nil, saslError(rc, nil, "cannot create new SASL client")
70 }
71 return ss, nil
72}
73
74func (ss *saslSession) cstr(s string) *C.char {
75 cstr := C.CString(s)
76 ss.cstrings = append(ss.cstrings, cstr)
77 return cstr
78}
79
80func (ss *saslSession) Close() {
81 for _, cstr := range ss.cstrings {
82 C.free(unsafe.Pointer(cstr))
83 }
84 ss.cstrings = nil
85
86 if ss.callbacks != nil {
87 C.free(unsafe.Pointer(ss.callbacks))
88 }
89
90 // The documentation of SASL dispose makes it clear that this should only
91 // be done when the connection is done, not when the authentication phase
92 // is done, because an encryption layer may have been negotiated.
93 // Even then, we'll do this for now, because it's simpler and prevents
94 // keeping track of this state for every socket. If it breaks, we'll fix it.
95 C.sasl_dispose(&ss.conn)
96}
97
98func (ss *saslSession) Step(serverData []byte) (clientData []byte, done bool, err error) {
99 ss.step++
100 if ss.step > 10 {
101 return nil, false, fmt.Errorf("too many SASL steps without authentication")
102 }
103 var cclientData *C.char
104 var cclientDataLen C.uint
105 var rc C.int
106 if ss.step == 1 {
107 var mechanism *C.char // ignored - must match cred
108 rc = C.sasl_client_start(ss.conn, ss.cstr(ss.mech), nil, &cclientData, &cclientDataLen, &mechanism)
109 } else {
110 var cserverData *C.char
111 var cserverDataLen C.uint
112 if len(serverData) > 0 {
113 cserverData = (*C.char)(unsafe.Pointer(&serverData[0]))
114 cserverDataLen = C.uint(len(serverData))
115 }
116 rc = C.sasl_client_step(ss.conn, cserverData, cserverDataLen, nil, &cclientData, &cclientDataLen)
117 }
118 if cclientData != nil && cclientDataLen > 0 {
119 clientData = C.GoBytes(unsafe.Pointer(cclientData), C.int(cclientDataLen))
120 }
121 if rc == C.SASL_OK {
122 return clientData, true, nil
123 }
124 if rc == C.SASL_CONTINUE {
125 return clientData, false, nil
126 }
127 return nil, false, saslError(rc, ss.conn, "cannot establish SASL session")
128}
129
130func saslError(rc C.int, conn *C.sasl_conn_t, msg string) error {
131 var detail string
132 if conn == nil {
133 detail = C.GoString(C.sasl_errstring(rc, nil, nil))
134 } else {
135 detail = C.GoString(C.sasl_errdetail(conn))
136 }
137 return fmt.Errorf(msg + ": " + detail)
138}
vendor/gopkg.in/mgo.v2/internal/sasl/sasl_windows.c+118−0
@@ -0,0 +1,118 @@
1#include "sasl_windows.h"
2
3static const LPSTR SSPI_PACKAGE_NAME = "kerberos";
4
5SECURITY_STATUS SEC_ENTRY sspi_acquire_credentials_handle(CredHandle *cred_handle, char *username, char *password, char *domain)
6{
7 SEC_WINNT_AUTH_IDENTITY auth_identity;
8 SECURITY_INTEGER ignored;
9
10 auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
11 auth_identity.User = (LPSTR) username;
12 auth_identity.UserLength = strlen(username);
13 auth_identity.Password = (LPSTR) password;
14 auth_identity.PasswordLength = strlen(password);
15 auth_identity.Domain = (LPSTR) domain;
16 auth_identity.DomainLength = strlen(domain);
17 return call_sspi_acquire_credentials_handle(NULL, SSPI_PACKAGE_NAME, SECPKG_CRED_OUTBOUND, NULL, &auth_identity, NULL, NULL, cred_handle, &ignored);
18}
19
20int sspi_step(CredHandle *cred_handle, int has_context, CtxtHandle *context, PVOID *buffer, ULONG *buffer_length, char *target)
21{
22 SecBufferDesc inbuf;
23 SecBuffer in_bufs[1];
24 SecBufferDesc outbuf;
25 SecBuffer out_bufs[1];
26
27 if (has_context > 0) {
28 // If we already have a context, we now have data to send.
29 // Put this data in an inbuf.
30 inbuf.ulVersion = SECBUFFER_VERSION;
31 inbuf.cBuffers = 1;
32 inbuf.pBuffers = in_bufs;
33 in_bufs[0].pvBuffer = *buffer;
34 in_bufs[0].cbBuffer = *buffer_length;
35 in_bufs[0].BufferType = SECBUFFER_TOKEN;
36 }
37
38 outbuf.ulVersion = SECBUFFER_VERSION;
39 outbuf.cBuffers = 1;
40 outbuf.pBuffers = out_bufs;
41 out_bufs[0].pvBuffer = NULL;
42 out_bufs[0].cbBuffer = 0;
43 out_bufs[0].BufferType = SECBUFFER_TOKEN;
44
45 ULONG context_attr = 0;
46
47 int ret = call_sspi_initialize_security_context(cred_handle,
48 has_context > 0 ? context : NULL,
49 (LPSTR) target,
50 ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_MUTUAL_AUTH,
51 0,
52 SECURITY_NETWORK_DREP,
53 has_context > 0 ? &inbuf : NULL,
54 0,
55 context,
56 &outbuf,
57 &context_attr,
58 NULL);
59
60 *buffer = malloc(out_bufs[0].cbBuffer);
61 *buffer_length = out_bufs[0].cbBuffer;
62 memcpy(*buffer, out_bufs[0].pvBuffer, *buffer_length);
63
64 return ret;
65}
66
67int sspi_send_client_authz_id(CtxtHandle *context, PVOID *buffer, ULONG *buffer_length, char *user_plus_realm)
68{
69 SecPkgContext_Sizes sizes;
70 SECURITY_STATUS status = call_sspi_query_context_attributes(context, SECPKG_ATTR_SIZES, &sizes);
71
72 if (status != SEC_E_OK) {
73 return status;
74 }
75
76 size_t user_plus_realm_length = strlen(user_plus_realm);
77 int msgSize = 4 + user_plus_realm_length;
78 char *msg = malloc((sizes.cbSecurityTrailer + msgSize + sizes.cbBlockSize) * sizeof(char));
79 msg[sizes.cbSecurityTrailer + 0] = 1;
80 msg[sizes.cbSecurityTrailer + 1] = 0;
81 msg[sizes.cbSecurityTrailer + 2] = 0;
82 msg[sizes.cbSecurityTrailer + 3] = 0;
83 memcpy(&msg[sizes.cbSecurityTrailer + 4], user_plus_realm, user_plus_realm_length);
84
85 SecBuffer wrapBufs[3];
86 SecBufferDesc wrapBufDesc;
87 wrapBufDesc.cBuffers = 3;
88 wrapBufDesc.pBuffers = wrapBufs;
89 wrapBufDesc.ulVersion = SECBUFFER_VERSION;
90
91 wrapBufs[0].cbBuffer = sizes.cbSecurityTrailer;
92 wrapBufs[0].BufferType = SECBUFFER_TOKEN;
93 wrapBufs[0].pvBuffer = msg;
94
95 wrapBufs[1].cbBuffer = msgSize;
96 wrapBufs[1].BufferType = SECBUFFER_DATA;
97 wrapBufs[1].pvBuffer = msg + sizes.cbSecurityTrailer;
98
99 wrapBufs[2].cbBuffer = sizes.cbBlockSize;
100 wrapBufs[2].BufferType = SECBUFFER_PADDING;
101 wrapBufs[2].pvBuffer = msg + sizes.cbSecurityTrailer + msgSize;
102
103 status = call_sspi_encrypt_message(context, SECQOP_WRAP_NO_ENCRYPT, &wrapBufDesc, 0);
104 if (status != SEC_E_OK) {
105 free(msg);
106 return status;
107 }
108
109 *buffer_length = wrapBufs[0].cbBuffer + wrapBufs[1].cbBuffer + wrapBufs[2].cbBuffer;
110 *buffer = malloc(*buffer_length);
111
112 memcpy(*buffer, wrapBufs[0].pvBuffer, wrapBufs[0].cbBuffer);
113 memcpy(*buffer + wrapBufs[0].cbBuffer, wrapBufs[1].pvBuffer, wrapBufs[1].cbBuffer);
114 memcpy(*buffer + wrapBufs[0].cbBuffer + wrapBufs[1].cbBuffer, wrapBufs[2].pvBuffer, wrapBufs[2].cbBuffer);
115
116 free(msg);
117 return SEC_E_OK;
118}
vendor/gopkg.in/mgo.v2/internal/sasl/sasl_windows.go+140−0
@@ -0,0 +1,140 @@
1package sasl
2
3// #include "sasl_windows.h"
4import "C"
5
6import (
7 "fmt"
8 "strings"
9 "sync"
10 "unsafe"
11)
12
13type saslStepper interface {
14 Step(serverData []byte) (clientData []byte, done bool, err error)
15 Close()
16}
17
18type saslSession struct {
19 // Credentials
20 mech string
21 service string
22 host string
23 userPlusRealm string
24 target string
25 domain string
26
27 // Internal state
28 authComplete bool
29 errored bool
30 step int
31
32 // C internal state
33 credHandle C.CredHandle
34 context C.CtxtHandle
35 hasContext C.int
36
37 // Keep track of pointers we need to explicitly free
38 stringsToFree []*C.char
39}
40
41var initError error
42var initOnce sync.Once
43
44func initSSPI() {
45 rc := C.load_secur32_dll()
46 if rc != 0 {
47 initError = fmt.Errorf("Error loading libraries: %v", rc)
48 }
49}
50
51func New(username, password, mechanism, service, host string) (saslStepper, error) {
52 initOnce.Do(initSSPI)
53 ss := &saslSession{mech: mechanism, hasContext: 0, userPlusRealm: username}
54 if service == "" {
55 service = "mongodb"
56 }
57 if i := strings.Index(host, ":"); i >= 0 {
58 host = host[:i]
59 }
60 ss.service = service
61 ss.host = host
62
63 usernameComponents := strings.Split(username, "@")
64 if len(usernameComponents) < 2 {
65 return nil, fmt.Errorf("Username '%v' doesn't contain a realm!", username)
66 }
67 user := usernameComponents[0]
68 ss.domain = usernameComponents[1]
69 ss.target = fmt.Sprintf("%s/%s", ss.service, ss.host)
70
71 var status C.SECURITY_STATUS
72 // Step 0: call AcquireCredentialsHandle to get a nice SSPI CredHandle
73 if len(password) > 0 {
74 status = C.sspi_acquire_credentials_handle(&ss.credHandle, ss.cstr(user), ss.cstr(password), ss.cstr(ss.domain))
75 } else {
76 status = C.sspi_acquire_credentials_handle(&ss.credHandle, ss.cstr(user), nil, ss.cstr(ss.domain))
77 }
78 if status != C.SEC_E_OK {
79 ss.errored = true
80 return nil, fmt.Errorf("Couldn't create new SSPI client, error code %v", status)
81 }
82 return ss, nil
83}
84
85func (ss *saslSession) cstr(s string) *C.char {
86 cstr := C.CString(s)
87 ss.stringsToFree = append(ss.stringsToFree, cstr)
88 return cstr
89}
90
91func (ss *saslSession) Close() {
92 for _, cstr := range ss.stringsToFree {
93 C.free(unsafe.Pointer(cstr))
94 }
95}
96
97func (ss *saslSession) Step(serverData []byte) (clientData []byte, done bool, err error) {
98 ss.step++
99 if ss.step > 10 {
100 return nil, false, fmt.Errorf("too many SSPI steps without authentication")
101 }
102 var buffer C.PVOID
103 var bufferLength C.ULONG
104 if len(serverData) > 0 {
105 buffer = (C.PVOID)(unsafe.Pointer(&serverData[0]))
106 bufferLength = C.ULONG(len(serverData))
107 }
108 var status C.int
109 if ss.authComplete {
110 // Step 3: last bit of magic to use the correct server credentials
111 status = C.sspi_send_client_authz_id(&ss.context, &buffer, &bufferLength, ss.cstr(ss.userPlusRealm))
112 } else {
113 // Step 1 + Step 2: set up security context with the server and TGT
114 status = C.sspi_step(&ss.credHandle, ss.hasContext, &ss.context, &buffer, &bufferLength, ss.cstr(ss.target))
115 }
116 if buffer != C.PVOID(nil) {
117 defer C.free(unsafe.Pointer(buffer))
118 }
119 if status != C.SEC_E_OK && status != C.SEC_I_CONTINUE_NEEDED {
120 ss.errored = true
121 return nil, false, ss.handleSSPIErrorCode(status)
122 }
123
124 clientData = C.GoBytes(unsafe.Pointer(buffer), C.int(bufferLength))
125 if status == C.SEC_E_OK {
126 ss.authComplete = true
127 return clientData, true, nil
128 } else {
129 ss.hasContext = 1
130 return clientData, false, nil
131 }
132}
133
134func (ss *saslSession) handleSSPIErrorCode(code C.int) error {
135 switch {
136 case code == C.SEC_E_TARGET_UNKNOWN:
137 return fmt.Errorf("Target %v@%v not found", ss.target, ss.domain)
138 }
139 return fmt.Errorf("Unknown error doing step %v, error code %v", ss.step, code)
140}
vendor/gopkg.in/mgo.v2/internal/sasl/sasl_windows.h+7−0
@@ -0,0 +1,7 @@
1#include <windows.h>
2
3#include "sspi_windows.h"
4
5SECURITY_STATUS SEC_ENTRY sspi_acquire_credentials_handle(CredHandle* cred_handle, char* username, char* password, char* domain);
6int sspi_step(CredHandle* cred_handle, int has_context, CtxtHandle* context, PVOID* buffer, ULONG* buffer_length, char* target);
7int sspi_send_client_authz_id(CtxtHandle* context, PVOID* buffer, ULONG* buffer_length, char* user_plus_realm);
vendor/gopkg.in/mgo.v2/internal/sasl/sspi_windows.c+96−0
@@ -0,0 +1,96 @@
1// Code adapted from the NodeJS kerberos library:
2//
3// https://github.com/christkv/kerberos/tree/master/lib/win32/kerberos_sspi.c
4//
5// Under the terms of the Apache License, Version 2.0:
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9#include <stdlib.h>
10
11#include "sspi_windows.h"
12
13static HINSTANCE sspi_secur32_dll = NULL;
14
15int load_secur32_dll()
16{
17 sspi_secur32_dll = LoadLibrary("secur32.dll");
18 if (sspi_secur32_dll == NULL) {
19 return GetLastError();
20 }
21 return 0;
22}
23
24SECURITY_STATUS SEC_ENTRY call_sspi_encrypt_message(PCtxtHandle phContext, unsigned long fQOP, PSecBufferDesc pMessage, unsigned long MessageSeqNo)
25{
26 if (sspi_secur32_dll == NULL) {
27 return -1;
28 }
29 encryptMessage_fn pfn_encryptMessage = (encryptMessage_fn) GetProcAddress(sspi_secur32_dll, "EncryptMessage");
30 if (!pfn_encryptMessage) {
31 return -2;
32 }
33 return (*pfn_encryptMessage)(phContext, fQOP, pMessage, MessageSeqNo);
34}
35
36SECURITY_STATUS SEC_ENTRY call_sspi_acquire_credentials_handle(
37 LPSTR pszPrincipal, LPSTR pszPackage, unsigned long fCredentialUse,
38 void *pvLogonId, void *pAuthData, SEC_GET_KEY_FN pGetKeyFn, void *pvGetKeyArgument,
39 PCredHandle phCredential, PTimeStamp ptsExpiry)
40{
41 if (sspi_secur32_dll == NULL) {
42 return -1;
43 }
44 acquireCredentialsHandle_fn pfn_acquireCredentialsHandle;
45#ifdef _UNICODE
46 pfn_acquireCredentialsHandle = (acquireCredentialsHandle_fn) GetProcAddress(sspi_secur32_dll, "AcquireCredentialsHandleW");
47#else
48 pfn_acquireCredentialsHandle = (acquireCredentialsHandle_fn) GetProcAddress(sspi_secur32_dll, "AcquireCredentialsHandleA");
49#endif
50 if (!pfn_acquireCredentialsHandle) {
51 return -2;
52 }
53 return (*pfn_acquireCredentialsHandle)(
54 pszPrincipal, pszPackage, fCredentialUse, pvLogonId, pAuthData,
55 pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry);
56}
57
58SECURITY_STATUS SEC_ENTRY call_sspi_initialize_security_context(
59 PCredHandle phCredential, PCtxtHandle phContext, LPSTR pszTargetName,
60 unsigned long fContextReq, unsigned long Reserved1, unsigned long TargetDataRep,
61 PSecBufferDesc pInput, unsigned long Reserved2, PCtxtHandle phNewContext,
62 PSecBufferDesc pOutput, unsigned long *pfContextAttr, PTimeStamp ptsExpiry)
63{
64 if (sspi_secur32_dll == NULL) {
65 return -1;
66 }
67 initializeSecurityContext_fn pfn_initializeSecurityContext;
68#ifdef _UNICODE
69 pfn_initializeSecurityContext = (initializeSecurityContext_fn) GetProcAddress(sspi_secur32_dll, "InitializeSecurityContextW");
70#else
71 pfn_initializeSecurityContext = (initializeSecurityContext_fn) GetProcAddress(sspi_secur32_dll, "InitializeSecurityContextA");
72#endif
73 if (!pfn_initializeSecurityContext) {
74 return -2;
75 }
76 return (*pfn_initializeSecurityContext)(
77 phCredential, phContext, pszTargetName, fContextReq, Reserved1, TargetDataRep,
78 pInput, Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
79}
80
81SECURITY_STATUS SEC_ENTRY call_sspi_query_context_attributes(PCtxtHandle phContext, unsigned long ulAttribute, void *pBuffer)
82{
83 if (sspi_secur32_dll == NULL) {
84 return -1;
85 }
86 queryContextAttributes_fn pfn_queryContextAttributes;
87#ifdef _UNICODE
88 pfn_queryContextAttributes = (queryContextAttributes_fn) GetProcAddress(sspi_secur32_dll, "QueryContextAttributesW");
89#else
90 pfn_queryContextAttributes = (queryContextAttributes_fn) GetProcAddress(sspi_secur32_dll, "QueryContextAttributesA");
91#endif
92 if (!pfn_queryContextAttributes) {
93 return -2;
94 }
95 return (*pfn_queryContextAttributes)(phContext, ulAttribute, pBuffer);
96}
vendor/gopkg.in/mgo.v2/internal/sasl/sspi_windows.h+70−0
@@ -0,0 +1,70 @@
1// Code adapted from the NodeJS kerberos library:
2//
3// https://github.com/christkv/kerberos/tree/master/lib/win32/kerberos_sspi.h
4//
5// Under the terms of the Apache License, Version 2.0:
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9#ifndef SSPI_WINDOWS_H
10#define SSPI_WINDOWS_H
11
12#define SECURITY_WIN32 1
13
14#include <windows.h>
15#include <sspi.h>
16
17int load_secur32_dll();
18
19SECURITY_STATUS SEC_ENTRY call_sspi_encrypt_message(PCtxtHandle phContext, unsigned long fQOP, PSecBufferDesc pMessage, unsigned long MessageSeqNo);
20
21typedef DWORD (WINAPI *encryptMessage_fn)(PCtxtHandle phContext, ULONG fQOP, PSecBufferDesc pMessage, ULONG MessageSeqNo);
22
23SECURITY_STATUS SEC_ENTRY call_sspi_acquire_credentials_handle(
24 LPSTR pszPrincipal, // Name of principal
25 LPSTR pszPackage, // Name of package
26 unsigned long fCredentialUse, // Flags indicating use
27 void *pvLogonId, // Pointer to logon ID
28 void *pAuthData, // Package specific data
29 SEC_GET_KEY_FN pGetKeyFn, // Pointer to GetKey() func
30 void *pvGetKeyArgument, // Value to pass to GetKey()
31 PCredHandle phCredential, // (out) Cred Handle
32 PTimeStamp ptsExpiry // (out) Lifetime (optional)
33);
34
35typedef DWORD (WINAPI *acquireCredentialsHandle_fn)(
36 LPSTR pszPrincipal, LPSTR pszPackage, unsigned long fCredentialUse,
37 void *pvLogonId, void *pAuthData, SEC_GET_KEY_FN pGetKeyFn, void *pvGetKeyArgument,
38 PCredHandle phCredential, PTimeStamp ptsExpiry
39);
40
41SECURITY_STATUS SEC_ENTRY call_sspi_initialize_security_context(
42 PCredHandle phCredential, // Cred to base context
43 PCtxtHandle phContext, // Existing context (OPT)
44 LPSTR pszTargetName, // Name of target
45 unsigned long fContextReq, // Context Requirements
46 unsigned long Reserved1, // Reserved, MBZ
47 unsigned long TargetDataRep, // Data rep of target
48 PSecBufferDesc pInput, // Input Buffers
49 unsigned long Reserved2, // Reserved, MBZ
50 PCtxtHandle phNewContext, // (out) New Context handle
51 PSecBufferDesc pOutput, // (inout) Output Buffers
52 unsigned long *pfContextAttr, // (out) Context attrs
53 PTimeStamp ptsExpiry // (out) Life span (OPT)
54);
55
56typedef DWORD (WINAPI *initializeSecurityContext_fn)(
57 PCredHandle phCredential, PCtxtHandle phContext, LPSTR pszTargetName, unsigned long fContextReq,
58 unsigned long Reserved1, unsigned long TargetDataRep, PSecBufferDesc pInput, unsigned long Reserved2,
59 PCtxtHandle phNewContext, PSecBufferDesc pOutput, unsigned long *pfContextAttr, PTimeStamp ptsExpiry);
60
61SECURITY_STATUS SEC_ENTRY call_sspi_query_context_attributes(
62 PCtxtHandle phContext, // Context to query
63 unsigned long ulAttribute, // Attribute to query
64 void *pBuffer // Buffer for attributes
65);
66
67typedef DWORD (WINAPI *queryContextAttributes_fn)(
68 PCtxtHandle phContext, unsigned long ulAttribute, void *pBuffer);
69
70#endif // SSPI_WINDOWS_H
vendor/gopkg.in/mgo.v2/internal/scram/scram.go+266−0
@@ -0,0 +1,266 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27// Pacakage scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
28//
29// http://tools.ietf.org/html/rfc5802
30//
31package scram
32
33import (
34 "bytes"
35 "crypto/hmac"
36 "crypto/rand"
37 "encoding/base64"
38 "fmt"
39 "hash"
40 "strconv"
41 "strings"
42)
43
44// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
45//
46// A Client may be used within a SASL conversation with logic resembling:
47//
48// var in []byte
49// var client = scram.NewClient(sha1.New, user, pass)
50// for client.Step(in) {
51// out := client.Out()
52// // send out to server
53// in := serverOut
54// }
55// if client.Err() != nil {
56// // auth failed
57// }
58//
59type Client struct {
60 newHash func() hash.Hash
61
62 user string
63 pass string
64 step int
65 out bytes.Buffer
66 err error
67
68 clientNonce []byte
69 serverNonce []byte
70 saltedPass []byte
71 authMsg bytes.Buffer
72}
73
74// NewClient returns a new SCRAM-* client with the provided hash algorithm.
75//
76// For SCRAM-SHA-1, for example, use:
77//
78// client := scram.NewClient(sha1.New, user, pass)
79//
80func NewClient(newHash func() hash.Hash, user, pass string) *Client {
81 c := &Client{
82 newHash: newHash,
83 user: user,
84 pass: pass,
85 }
86 c.out.Grow(256)
87 c.authMsg.Grow(256)
88 return c
89}
90
91// Out returns the data to be sent to the server in the current step.
92func (c *Client) Out() []byte {
93 if c.out.Len() == 0 {
94 return nil
95 }
96 return c.out.Bytes()
97}
98
99// Err returns the error that ocurred, or nil if there were no errors.
100func (c *Client) Err() error {
101 return c.err
102}
103
104// SetNonce sets the client nonce to the provided value.
105// If not set, the nonce is generated automatically out of crypto/rand on the first step.
106func (c *Client) SetNonce(nonce []byte) {
107 c.clientNonce = nonce
108}
109
110var escaper = strings.NewReplacer("=", "=3D", ",", "=2C")
111
112// Step processes the incoming data from the server and makes the
113// next round of data for the server available via Client.Out.
114// Step returns false if there are no errors and more data is
115// still expected.
116func (c *Client) Step(in []byte) bool {
117 c.out.Reset()
118 if c.step > 2 || c.err != nil {
119 return false
120 }
121 c.step++
122 switch c.step {
123 case 1:
124 c.err = c.step1(in)
125 case 2:
126 c.err = c.step2(in)
127 case 3:
128 c.err = c.step3(in)
129 }
130 return c.step > 2 || c.err != nil
131}
132
133func (c *Client) step1(in []byte) error {
134 if len(c.clientNonce) == 0 {
135 const nonceLen = 6
136 buf := make([]byte, nonceLen + b64.EncodedLen(nonceLen))
137 if _, err := rand.Read(buf[:nonceLen]); err != nil {
138 return fmt.Errorf("cannot read random SCRAM-SHA-1 nonce from operating system: %v", err)
139 }
140 c.clientNonce = buf[nonceLen:]
141 b64.Encode(c.clientNonce, buf[:nonceLen])
142 }
143 c.authMsg.WriteString("n=")
144 escaper.WriteString(&c.authMsg, c.user)
145 c.authMsg.WriteString(",r=")
146 c.authMsg.Write(c.clientNonce)
147
148 c.out.WriteString("n,,")
149 c.out.Write(c.authMsg.Bytes())
150 return nil
151}
152
153var b64 = base64.StdEncoding
154
155func (c *Client) step2(in []byte) error {
156 c.authMsg.WriteByte(',')
157 c.authMsg.Write(in)
158
159 fields := bytes.Split(in, []byte(","))
160 if len(fields) != 3 {
161 return fmt.Errorf("expected 3 fields in first SCRAM-SHA-1 server message, got %d: %q", len(fields), in)
162 }
163 if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 {
164 return fmt.Errorf("server sent an invalid SCRAM-SHA-1 nonce: %q", fields[0])
165 }
166 if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 {
167 return fmt.Errorf("server sent an invalid SCRAM-SHA-1 salt: %q", fields[1])
168 }
169 if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 {
170 return fmt.Errorf("server sent an invalid SCRAM-SHA-1 iteration count: %q", fields[2])
171 }
172
173 c.serverNonce = fields[0][2:]
174 if !bytes.HasPrefix(c.serverNonce, c.clientNonce) {
175 return fmt.Errorf("server SCRAM-SHA-1 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce)
176 }
177
178 salt := make([]byte, b64.DecodedLen(len(fields[1][2:])))
179 n, err := b64.Decode(salt, fields[1][2:])
180 if err != nil {
181 return fmt.Errorf("cannot decode SCRAM-SHA-1 salt sent by server: %q", fields[1])
182 }
183 salt = salt[:n]
184 iterCount, err := strconv.Atoi(string(fields[2][2:]))
185 if err != nil {
186 return fmt.Errorf("server sent an invalid SCRAM-SHA-1 iteration count: %q", fields[2])
187 }
188 c.saltPassword(salt, iterCount)
189
190 c.authMsg.WriteString(",c=biws,r=")
191 c.authMsg.Write(c.serverNonce)
192
193 c.out.WriteString("c=biws,r=")
194 c.out.Write(c.serverNonce)
195 c.out.WriteString(",p=")
196 c.out.Write(c.clientProof())
197 return nil
198}
199
200func (c *Client) step3(in []byte) error {
201 var isv, ise bool
202 var fields = bytes.Split(in, []byte(","))
203 if len(fields) == 1 {
204 isv = bytes.HasPrefix(fields[0], []byte("v="))
205 ise = bytes.HasPrefix(fields[0], []byte("e="))
206 }
207 if ise {
208 return fmt.Errorf("SCRAM-SHA-1 authentication error: %s", fields[0][2:])
209 } else if !isv {
210 return fmt.Errorf("unsupported SCRAM-SHA-1 final message from server: %q", in)
211 }
212 if !bytes.Equal(c.serverSignature(), fields[0][2:]) {
213 return fmt.Errorf("cannot authenticate SCRAM-SHA-1 server signature: %q", fields[0][2:])
214 }
215 return nil
216}
217
218func (c *Client) saltPassword(salt []byte, iterCount int) {
219 mac := hmac.New(c.newHash, []byte(c.pass))
220 mac.Write(salt)
221 mac.Write([]byte{0, 0, 0, 1})
222 ui := mac.Sum(nil)
223 hi := make([]byte, len(ui))
224 copy(hi, ui)
225 for i := 1; i < iterCount; i++ {
226 mac.Reset()
227 mac.Write(ui)
228 mac.Sum(ui[:0])
229 for j, b := range ui {
230 hi[j] ^= b
231 }
232 }
233 c.saltedPass = hi
234}
235
236func (c *Client) clientProof() []byte {
237 mac := hmac.New(c.newHash, c.saltedPass)
238 mac.Write([]byte("Client Key"))
239 clientKey := mac.Sum(nil)
240 hash := c.newHash()
241 hash.Write(clientKey)
242 storedKey := hash.Sum(nil)
243 mac = hmac.New(c.newHash, storedKey)
244 mac.Write(c.authMsg.Bytes())
245 clientProof := mac.Sum(nil)
246 for i, b := range clientKey {
247 clientProof[i] ^= b
248 }
249 clientProof64 := make([]byte, b64.EncodedLen(len(clientProof)))
250 b64.Encode(clientProof64, clientProof)
251 return clientProof64
252}
253
254func (c *Client) serverSignature() []byte {
255 mac := hmac.New(c.newHash, c.saltedPass)
256 mac.Write([]byte("Server Key"))
257 serverKey := mac.Sum(nil)
258
259 mac = hmac.New(c.newHash, serverKey)
260 mac.Write(c.authMsg.Bytes())
261 serverSignature := mac.Sum(nil)
262
263 encoded := make([]byte, b64.EncodedLen(len(serverSignature)))
264 b64.Encode(encoded, serverSignature)
265 return encoded
266}
vendor/gopkg.in/mgo.v2/internal/scram/scram_test.go+67−0
@@ -0,0 +1,67 @@
1package scram_test
2
3import (
4 "crypto/sha1"
5 "testing"
6
7 . "gopkg.in/check.v1"
8 "gopkg.in/mgo.v2/internal/scram"
9 "strings"
10)
11
12var _ = Suite(&S{})
13
14func Test(t *testing.T) { TestingT(t) }
15
16type S struct{}
17
18var tests = [][]string{{
19 "U: user pencil",
20 "N: fyko+d2lbbFgONRv9qkxdawL",
21 "C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL",
22 "S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096",
23 "C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=",
24 "S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=",
25}, {
26 "U: root fe8c89e308ec08763df36333cbf5d3a2",
27 "N: OTcxNDk5NjM2MzE5",
28 "C: n,,n=root,r=OTcxNDk5NjM2MzE5",
29 "S: r=OTcxNDk5NjM2MzE581Ra3provgG0iDsMkDiIAlrh4532dDLp,s=XRDkVrFC9JuL7/F4tG0acQ==,i=10000",
30 "C: c=biws,r=OTcxNDk5NjM2MzE581Ra3provgG0iDsMkDiIAlrh4532dDLp,p=6y1jp9R7ETyouTXS9fW9k5UHdBc=",
31 "S: v=LBnd9dUJRxdqZiEq91NKP3z/bHA=",
32}}
33
34func (s *S) TestExamples(c *C) {
35 for _, steps := range tests {
36 if len(steps) < 2 || len(steps[0]) < 3 || !strings.HasPrefix(steps[0], "U: ") {
37 c.Fatalf("Invalid test: %#v", steps)
38 }
39 auth := strings.Fields(steps[0][3:])
40 client := scram.NewClient(sha1.New, auth[0], auth[1])
41 first, done := true, false
42 c.Logf("-----")
43 c.Logf("%s", steps[0])
44 for _, step := range steps[1:] {
45 c.Logf("%s", step)
46 switch step[:3] {
47 case "N: ":
48 client.SetNonce([]byte(step[3:]))
49 case "C: ":
50 if first {
51 first = false
52 done = client.Step(nil)
53 }
54 c.Assert(done, Equals, false)
55 c.Assert(client.Err(), IsNil)
56 c.Assert(string(client.Out()), Equals, step[3:])
57 case "S: ":
58 first = false
59 done = client.Step([]byte(step[3:]))
60 default:
61 panic("invalid test line: " + step)
62 }
63 }
64 c.Assert(done, Equals, true)
65 c.Assert(client.Err(), IsNil)
66 }
67}
vendor/gopkg.in/mgo.v2/log.go+133−0
@@ -0,0 +1,133 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29import (
30 "fmt"
31 "sync"
32)
33
34// ---------------------------------------------------------------------------
35// Logging integration.
36
37// Avoid importing the log type information unnecessarily. There's a small cost
38// associated with using an interface rather than the type. Depending on how
39// often the logger is plugged in, it would be worth using the type instead.
40type log_Logger interface {
41 Output(calldepth int, s string) error
42}
43
44var (
45 globalLogger log_Logger
46 globalDebug bool
47 globalMutex sync.Mutex
48)
49
50// RACE WARNING: There are known data races when logging, which are manually
51// silenced when the race detector is in use. These data races won't be
52// observed in typical use, because logging is supposed to be set up once when
53// the application starts. Having raceDetector as a constant, the compiler
54// should elide the locks altogether in actual use.
55
56// Specify the *log.Logger object where log messages should be sent to.
57func SetLogger(logger log_Logger) {
58 if raceDetector {
59 globalMutex.Lock()
60 defer globalMutex.Unlock()
61 }
62 globalLogger = logger
63}
64
65// Enable the delivery of debug messages to the logger. Only meaningful
66// if a logger is also set.
67func SetDebug(debug bool) {
68 if raceDetector {
69 globalMutex.Lock()
70 defer globalMutex.Unlock()
71 }
72 globalDebug = debug
73}
74
75func log(v ...interface{}) {
76 if raceDetector {
77 globalMutex.Lock()
78 defer globalMutex.Unlock()
79 }
80 if globalLogger != nil {
81 globalLogger.Output(2, fmt.Sprint(v...))
82 }
83}
84
85func logln(v ...interface{}) {
86 if raceDetector {
87 globalMutex.Lock()
88 defer globalMutex.Unlock()
89 }
90 if globalLogger != nil {
91 globalLogger.Output(2, fmt.Sprintln(v...))
92 }
93}
94
95func logf(format string, v ...interface{}) {
96 if raceDetector {
97 globalMutex.Lock()
98 defer globalMutex.Unlock()
99 }
100 if globalLogger != nil {
101 globalLogger.Output(2, fmt.Sprintf(format, v...))
102 }
103}
104
105func debug(v ...interface{}) {
106 if raceDetector {
107 globalMutex.Lock()
108 defer globalMutex.Unlock()
109 }
110 if globalDebug && globalLogger != nil {
111 globalLogger.Output(2, fmt.Sprint(v...))
112 }
113}
114
115func debugln(v ...interface{}) {
116 if raceDetector {
117 globalMutex.Lock()
118 defer globalMutex.Unlock()
119 }
120 if globalDebug && globalLogger != nil {
121 globalLogger.Output(2, fmt.Sprintln(v...))
122 }
123}
124
125func debugf(format string, v ...interface{}) {
126 if raceDetector {
127 globalMutex.Lock()
128 defer globalMutex.Unlock()
129 }
130 if globalDebug && globalLogger != nil {
131 globalLogger.Output(2, fmt.Sprintf(format, v...))
132 }
133}
vendor/gopkg.in/mgo.v2/queue.go+91−0
@@ -0,0 +1,91 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29type queue struct {
30 elems []interface{}
31 nelems, popi, pushi int
32}
33
34func (q *queue) Len() int {
35 return q.nelems
36}
37
38func (q *queue) Push(elem interface{}) {
39 //debugf("Pushing(pushi=%d popi=%d cap=%d): %#v\n",
40 // q.pushi, q.popi, len(q.elems), elem)
41 if q.nelems == len(q.elems) {
42 q.expand()
43 }
44 q.elems[q.pushi] = elem
45 q.nelems++
46 q.pushi = (q.pushi + 1) % len(q.elems)
47 //debugf(" Pushed(pushi=%d popi=%d cap=%d): %#v\n",
48 // q.pushi, q.popi, len(q.elems), elem)
49}
50
51func (q *queue) Pop() (elem interface{}) {
52 //debugf("Popping(pushi=%d popi=%d cap=%d)\n",
53 // q.pushi, q.popi, len(q.elems))
54 if q.nelems == 0 {
55 return nil
56 }
57 elem = q.elems[q.popi]
58 q.elems[q.popi] = nil // Help GC.
59 q.nelems--
60 q.popi = (q.popi + 1) % len(q.elems)
61 //debugf(" Popped(pushi=%d popi=%d cap=%d): %#v\n",
62 // q.pushi, q.popi, len(q.elems), elem)
63 return elem
64}
65
66func (q *queue) expand() {
67 curcap := len(q.elems)
68 var newcap int
69 if curcap == 0 {
70 newcap = 8
71 } else if curcap < 1024 {
72 newcap = curcap * 2
73 } else {
74 newcap = curcap + (curcap / 4)
75 }
76 elems := make([]interface{}, newcap)
77
78 if q.popi == 0 {
79 copy(elems, q.elems)
80 q.pushi = curcap
81 } else {
82 newpopi := newcap - (curcap - q.popi)
83 copy(elems, q.elems[:q.popi])
84 copy(elems[newpopi:], q.elems[q.popi:])
85 q.popi = newpopi
86 }
87 for i := range q.elems {
88 q.elems[i] = nil // Help GC.
89 }
90 q.elems = elems
91}
vendor/gopkg.in/mgo.v2/queue_test.go+101−0
@@ -0,0 +1,101 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29import (
30 . "gopkg.in/check.v1"
31)
32
33type QS struct{}
34
35var _ = Suite(&QS{})
36
37func (s *QS) TestSequentialGrowth(c *C) {
38 q := queue{}
39 n := 2048
40 for i := 0; i != n; i++ {
41 q.Push(i)
42 }
43 for i := 0; i != n; i++ {
44 c.Assert(q.Pop(), Equals, i)
45 }
46}
47
48var queueTestLists = [][]int{
49 // {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
50 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
51
52 // {8, 9, 10, 11, ... 2, 3, 4, 5, 6, 7}
53 {0, 1, 2, 3, 4, 5, 6, 7, -1, -1, 8, 9, 10, 11},
54
55 // {8, 9, 10, 11, ... 2, 3, 4, 5, 6, 7}
56 {0, 1, 2, 3, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11},
57
58 // {0, 1, 2, 3, 4, 5, 6, 7, 8}
59 {0, 1, 2, 3, 4, 5, 6, 7, 8,
60 -1, -1, -1, -1, -1, -1, -1, -1, -1,
61 0, 1, 2, 3, 4, 5, 6, 7, 8},
62}
63
64func (s *QS) TestQueueTestLists(c *C) {
65 test := []int{}
66 testi := 0
67 reset := func() {
68 test = test[0:0]
69 testi = 0
70 }
71 push := func(i int) {
72 test = append(test, i)
73 }
74 pop := func() (i int) {
75 if testi == len(test) {
76 return -1
77 }
78 i = test[testi]
79 testi++
80 return
81 }
82
83 for _, list := range queueTestLists {
84 reset()
85 q := queue{}
86 for _, n := range list {
87 if n == -1 {
88 c.Assert(q.Pop(), Equals, pop(), Commentf("With list %#v", list))
89 } else {
90 q.Push(n)
91 push(n)
92 }
93 }
94
95 for n := pop(); n != -1; n = pop() {
96 c.Assert(q.Pop(), Equals, n, Commentf("With list %#v", list))
97 }
98
99 c.Assert(q.Pop(), Equals, nil, Commentf("With list %#v", list))
100 }
101}
vendor/gopkg.in/mgo.v2/raceoff.go+5−0
@@ -0,0 +1,5 @@
1// +build !race
2
3package mgo
4
5const raceDetector = false
vendor/gopkg.in/mgo.v2/raceon.go+5−0
@@ -0,0 +1,5 @@
1// +build race
2
3package mgo
4
5const raceDetector = true
vendor/gopkg.in/mgo.v2/saslimpl.go+11−0
@@ -0,0 +1,11 @@
1//+build sasl
2
3package mgo
4
5import (
6 "gopkg.in/mgo.v2/internal/sasl"
7)
8
9func saslNew(cred Credential, host string) (saslStepper, error) {
10 return sasl.New(cred.Username, cred.Password, cred.Mechanism, cred.Service, host)
11}
vendor/gopkg.in/mgo.v2/saslstub.go+11−0
@@ -0,0 +1,11 @@
1//+build !sasl
2
3package mgo
4
5import (
6 "fmt"
7)
8
9func saslNew(cred Credential, host string) (saslStepper, error) {
10 return nil, fmt.Errorf("SASL support not enabled during build (-tags sasl)")
11}
vendor/gopkg.in/mgo.v2/server.go+448−0
@@ -0,0 +1,448 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29import (
30 "errors"
31 "net"
32 "sort"
33 "sync"
34 "time"
35
36 "gopkg.in/mgo.v2/bson"
37)
38
39// ---------------------------------------------------------------------------
40// Mongo server encapsulation.
41
42type mongoServer struct {
43 sync.RWMutex
44 Addr string
45 ResolvedAddr string
46 tcpaddr *net.TCPAddr
47 unusedSockets []*mongoSocket
48 liveSockets []*mongoSocket
49 closed bool
50 abended bool
51 sync chan bool
52 dial dialer
53 pingValue time.Duration
54 pingIndex int
55 pingCount uint32
56 pingWindow [6]time.Duration
57 info *mongoServerInfo
58}
59
60type dialer struct {
61 old func(addr net.Addr) (net.Conn, error)
62 new func(addr *ServerAddr) (net.Conn, error)
63}
64
65func (dial dialer) isSet() bool {
66 return dial.old != nil || dial.new != nil
67}
68
69type mongoServerInfo struct {
70 Master bool
71 Mongos bool
72 Tags bson.D
73 MaxWireVersion int
74 SetName string
75}
76
77var defaultServerInfo mongoServerInfo
78
79func newServer(addr string, tcpaddr *net.TCPAddr, sync chan bool, dial dialer) *mongoServer {
80 server := &mongoServer{
81 Addr: addr,
82 ResolvedAddr: tcpaddr.String(),
83 tcpaddr: tcpaddr,
84 sync: sync,
85 dial: dial,
86 info: &defaultServerInfo,
87 }
88 // Once so the server gets a ping value, then loop in background.
89 server.pinger(false)
90 go server.pinger(true)
91 return server
92}
93
94var errPoolLimit = errors.New("per-server connection limit reached")
95var errServerClosed = errors.New("server was closed")
96
97// AcquireSocket returns a socket for communicating with the server.
98// This will attempt to reuse an old connection, if one is available. Otherwise,
99// it will establish a new one. The returned socket is owned by the call site,
100// and will return to the cache when the socket has its Release method called
101// the same number of times as AcquireSocket + Acquire were called for it.
102// If the poolLimit argument is greater than zero and the number of sockets in
103// use in this server is greater than the provided limit, errPoolLimit is
104// returned.
105func (server *mongoServer) AcquireSocket(poolLimit int, timeout time.Duration) (socket *mongoSocket, abended bool, err error) {
106 for {
107 server.Lock()
108 abended = server.abended
109 if server.closed {
110 server.Unlock()
111 return nil, abended, errServerClosed
112 }
113 n := len(server.unusedSockets)
114 if poolLimit > 0 && len(server.liveSockets)-n >= poolLimit {
115 server.Unlock()
116 return nil, false, errPoolLimit
117 }
118 if n > 0 {
119 socket = server.unusedSockets[n-1]
120 server.unusedSockets[n-1] = nil // Help GC.
121 server.unusedSockets = server.unusedSockets[:n-1]
122 info := server.info
123 server.Unlock()
124 err = socket.InitialAcquire(info, timeout)
125 if err != nil {
126 continue
127 }
128 } else {
129 server.Unlock()
130 socket, err = server.Connect(timeout)
131 if err == nil {
132 server.Lock()
133 // We've waited for the Connect, see if we got
134 // closed in the meantime
135 if server.closed {
136 server.Unlock()
137 socket.Release()
138 socket.Close()
139 return nil, abended, errServerClosed
140 }
141 server.liveSockets = append(server.liveSockets, socket)
142 server.Unlock()
143 }
144 }
145 return
146 }
147 panic("unreachable")
148}
149
150// Connect establishes a new connection to the server. This should
151// generally be done through server.AcquireSocket().
152func (server *mongoServer) Connect(timeout time.Duration) (*mongoSocket, error) {
153 server.RLock()
154 master := server.info.Master
155 dial := server.dial
156 server.RUnlock()
157
158 logf("Establishing new connection to %s (timeout=%s)...", server.Addr, timeout)
159 var conn net.Conn
160 var err error
161 switch {
162 case !dial.isSet():
163 // Cannot do this because it lacks timeout support. :-(
164 //conn, err = net.DialTCP("tcp", nil, server.tcpaddr)
165 conn, err = net.DialTimeout("tcp", server.ResolvedAddr, timeout)
166 case dial.old != nil:
167 conn, err = dial.old(server.tcpaddr)
168 case dial.new != nil:
169 conn, err = dial.new(&ServerAddr{server.Addr, server.tcpaddr})
170 default:
171 panic("dialer is set, but both dial.old and dial.new are nil")
172 }
173 if err != nil {
174 logf("Connection to %s failed: %v", server.Addr, err.Error())
175 return nil, err
176 }
177 logf("Connection to %s established.", server.Addr)
178
179 stats.conn(+1, master)
180 return newSocket(server, conn, timeout), nil
181}
182
183// Close forces closing all sockets that are alive, whether
184// they're currently in use or not.
185func (server *mongoServer) Close() {
186 server.Lock()
187 server.closed = true
188 liveSockets := server.liveSockets
189 unusedSockets := server.unusedSockets
190 server.liveSockets = nil
191 server.unusedSockets = nil
192 server.Unlock()
193 logf("Connections to %s closing (%d live sockets).", server.Addr, len(liveSockets))
194 for i, s := range liveSockets {
195 s.Close()
196 liveSockets[i] = nil
197 }
198 for i := range unusedSockets {
199 unusedSockets[i] = nil
200 }
201}
202
203// RecycleSocket puts socket back into the unused cache.
204func (server *mongoServer) RecycleSocket(socket *mongoSocket) {
205 server.Lock()
206 if !server.closed {
207 server.unusedSockets = append(server.unusedSockets, socket)
208 }
209 server.Unlock()
210}
211
212func removeSocket(sockets []*mongoSocket, socket *mongoSocket) []*mongoSocket {
213 for i, s := range sockets {
214 if s == socket {
215 copy(sockets[i:], sockets[i+1:])
216 n := len(sockets) - 1
217 sockets[n] = nil
218 sockets = sockets[:n]
219 break
220 }
221 }
222 return sockets
223}
224
225// AbendSocket notifies the server that the given socket has terminated
226// abnormally, and thus should be discarded rather than cached.
227func (server *mongoServer) AbendSocket(socket *mongoSocket) {
228 server.Lock()
229 server.abended = true
230 if server.closed {
231 server.Unlock()
232 return
233 }
234 server.liveSockets = removeSocket(server.liveSockets, socket)
235 server.unusedSockets = removeSocket(server.unusedSockets, socket)
236 server.Unlock()
237 // Maybe just a timeout, but suggest a cluster sync up just in case.
238 select {
239 case server.sync <- true:
240 default:
241 }
242}
243
244func (server *mongoServer) SetInfo(info *mongoServerInfo) {
245 server.Lock()
246 server.info = info
247 server.Unlock()
248}
249
250func (server *mongoServer) Info() *mongoServerInfo {
251 server.Lock()
252 info := server.info
253 server.Unlock()
254 return info
255}
256
257func (server *mongoServer) hasTags(serverTags []bson.D) bool {
258NextTagSet:
259 for _, tags := range serverTags {
260 NextReqTag:
261 for _, req := range tags {
262 for _, has := range server.info.Tags {
263 if req.Name == has.Name {
264 if req.Value == has.Value {
265 continue NextReqTag
266 }
267 continue NextTagSet
268 }
269 }
270 continue NextTagSet
271 }
272 return true
273 }
274 return false
275}
276
277var pingDelay = 5 * time.Second
278
279func (server *mongoServer) pinger(loop bool) {
280 var delay time.Duration
281 if raceDetector {
282 // This variable is only ever touched by tests.
283 globalMutex.Lock()
284 delay = pingDelay
285 globalMutex.Unlock()
286 } else {
287 delay = pingDelay
288 }
289 op := queryOp{
290 collection: "admin.$cmd",
291 query: bson.D{{"ping", 1}},
292 flags: flagSlaveOk,
293 limit: -1,
294 }
295 for {
296 if loop {
297 time.Sleep(delay)
298 }
299 op := op
300 socket, _, err := server.AcquireSocket(0, 3*delay)
301 if err == nil {
302 start := time.Now()
303 _, _ = socket.SimpleQuery(&op)
304 delay := time.Now().Sub(start)
305
306 server.pingWindow[server.pingIndex] = delay
307 server.pingIndex = (server.pingIndex + 1) % len(server.pingWindow)
308 server.pingCount++
309 var max time.Duration
310 for i := 0; i < len(server.pingWindow) && uint32(i) < server.pingCount; i++ {
311 if server.pingWindow[i] > max {
312 max = server.pingWindow[i]
313 }
314 }
315 socket.Release()
316 server.Lock()
317 if server.closed {
318 loop = false
319 }
320 server.pingValue = max
321 server.Unlock()
322 logf("Ping for %s is %d ms", server.Addr, max/time.Millisecond)
323 } else if err == errServerClosed {
324 return
325 }
326 if !loop {
327 return
328 }
329 }
330}
331
332type mongoServerSlice []*mongoServer
333
334func (s mongoServerSlice) Len() int {
335 return len(s)
336}
337
338func (s mongoServerSlice) Less(i, j int) bool {
339 return s[i].ResolvedAddr < s[j].ResolvedAddr
340}
341
342func (s mongoServerSlice) Swap(i, j int) {
343 s[i], s[j] = s[j], s[i]
344}
345
346func (s mongoServerSlice) Sort() {
347 sort.Sort(s)
348}
349
350func (s mongoServerSlice) Search(resolvedAddr string) (i int, ok bool) {
351 n := len(s)
352 i = sort.Search(n, func(i int) bool {
353 return s[i].ResolvedAddr >= resolvedAddr
354 })
355 return i, i != n && s[i].ResolvedAddr == resolvedAddr
356}
357
358type mongoServers struct {
359 slice mongoServerSlice
360}
361
362func (servers *mongoServers) Search(resolvedAddr string) (server *mongoServer) {
363 if i, ok := servers.slice.Search(resolvedAddr); ok {
364 return servers.slice[i]
365 }
366 return nil
367}
368
369func (servers *mongoServers) Add(server *mongoServer) {
370 servers.slice = append(servers.slice, server)
371 servers.slice.Sort()
372}
373
374func (servers *mongoServers) Remove(other *mongoServer) (server *mongoServer) {
375 if i, found := servers.slice.Search(other.ResolvedAddr); found {
376 server = servers.slice[i]
377 copy(servers.slice[i:], servers.slice[i+1:])
378 n := len(servers.slice) - 1
379 servers.slice[n] = nil // Help GC.
380 servers.slice = servers.slice[:n]
381 }
382 return
383}
384
385func (servers *mongoServers) Slice() []*mongoServer {
386 return ([]*mongoServer)(servers.slice)
387}
388
389func (servers *mongoServers) Get(i int) *mongoServer {
390 return servers.slice[i]
391}
392
393func (servers *mongoServers) Len() int {
394 return len(servers.slice)
395}
396
397func (servers *mongoServers) Empty() bool {
398 return len(servers.slice) == 0
399}
400
401// BestFit returns the best guess of what would be the most interesting
402// server to perform operations on at this point in time.
403func (servers *mongoServers) BestFit(serverTags []bson.D) *mongoServer {
404 var best *mongoServer
405 for _, next := range servers.slice {
406 if best == nil {
407 best = next
408 best.RLock()
409 if serverTags != nil && !next.info.Mongos && !best.hasTags(serverTags) {
410 best.RUnlock()
411 best = nil
412 }
413 continue
414 }
415 next.RLock()
416 swap := false
417 switch {
418 case serverTags != nil && !next.info.Mongos && !next.hasTags(serverTags):
419 // Must have requested tags.
420 case next.info.Master != best.info.Master:
421 // Prefer slaves.
422 swap = best.info.Master
423 case absDuration(next.pingValue-best.pingValue) > 15*time.Millisecond:
424 // Prefer nearest server.
425 swap = next.pingValue < best.pingValue
426 case len(next.liveSockets)-len(next.unusedSockets) < len(best.liveSockets)-len(best.unusedSockets):
427 // Prefer servers with less connections.
428 swap = true
429 }
430 if swap {
431 best.RUnlock()
432 best = next
433 } else {
434 next.RUnlock()
435 }
436 }
437 if best != nil {
438 best.RUnlock()
439 }
440 return best
441}
442
443func absDuration(d time.Duration) time.Duration {
444 if d < 0 {
445 return -d
446 }
447 return d
448}
vendor/gopkg.in/mgo.v2/session.go+4224−0
@@ -0,0 +1,4224 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo
28
29import (
30 "crypto/md5"
31 "encoding/hex"
32 "errors"
33 "fmt"
34 "math"
35 "net"
36 "net/url"
37 "reflect"
38 "sort"
39 "strconv"
40 "strings"
41 "sync"
42 "time"
43
44 "gopkg.in/mgo.v2/bson"
45)
46
47type mode int
48
49const (
50 Eventual mode = 0
51 Monotonic mode = 1
52 Strong mode = 2
53)
54
55// When changing the Session type, check if newSession and copySession
56// need to be updated too.
57
58type Session struct {
59 m sync.RWMutex
60 cluster_ *mongoCluster
61 slaveSocket *mongoSocket
62 masterSocket *mongoSocket
63 slaveOk bool
64 consistency mode
65 queryConfig query
66 safeOp *queryOp
67 syncTimeout time.Duration
68 sockTimeout time.Duration
69 defaultdb string
70 sourcedb string
71 dialCred *Credential
72 creds []Credential
73 poolLimit int
74}
75
76type Database struct {
77 Session *Session
78 Name string
79}
80
81type Collection struct {
82 Database *Database
83 Name string // "collection"
84 FullName string // "db.collection"
85}
86
87type Query struct {
88 m sync.Mutex
89 session *Session
90 query // Enables default settings in session.
91}
92
93type query struct {
94 op queryOp
95 prefetch float64
96 limit int32
97}
98
99type getLastError struct {
100 CmdName int "getLastError,omitempty"
101 W interface{} "w,omitempty"
102 WTimeout int "wtimeout,omitempty"
103 FSync bool "fsync,omitempty"
104 J bool "j,omitempty"
105}
106
107type Iter struct {
108 m sync.Mutex
109 gotReply sync.Cond
110 session *Session
111 server *mongoServer
112 docData queue
113 err error
114 op getMoreOp
115 prefetch float64
116 limit int32
117 docsToReceive int
118 docsBeforeMore int
119 timeout time.Duration
120 timedout bool
121}
122
123var (
124 ErrNotFound = errors.New("not found")
125 ErrCursor = errors.New("invalid cursor")
126)
127
128const defaultPrefetch = 0.25
129
130// Dial establishes a new session to the cluster identified by the given seed
131// server(s). The session will enable communication with all of the servers in
132// the cluster, so the seed servers are used only to find out about the cluster
133// topology.
134//
135// Dial will timeout after 10 seconds if a server isn't reached. The returned
136// session will timeout operations after one minute by default if servers
137// aren't available. To customize the timeout, see DialWithTimeout,
138// SetSyncTimeout, and SetSocketTimeout.
139//
140// This method is generally called just once for a given cluster. Further
141// sessions to the same cluster are then established using the New or Copy
142// methods on the obtained session. This will make them share the underlying
143// cluster, and manage the pool of connections appropriately.
144//
145// Once the session is not useful anymore, Close must be called to release the
146// resources appropriately.
147//
148// The seed servers must be provided in the following format:
149//
150// [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
151//
152// For example, it may be as simple as:
153//
154// localhost
155//
156// Or more involved like:
157//
158// mongodb://myuser:mypass@localhost:40001,otherhost:40001/mydb
159//
160// If the port number is not provided for a server, it defaults to 27017.
161//
162// The username and password provided in the URL will be used to authenticate
163// into the database named after the slash at the end of the host names, or
164// into the "admin" database if none is provided. The authentication information
165// will persist in sessions obtained through the New method as well.
166//
167// The following connection options are supported after the question mark:
168//
169// connect=direct
170//
171// Disables the automatic replica set server discovery logic, and
172// forces the use of servers provided only (even if secondaries).
173// Note that to talk to a secondary the consistency requirements
174// must be relaxed to Monotonic or Eventual via SetMode.
175//
176//
177// authSource=<db>
178//
179// Informs the database used to establish credentials and privileges
180// with a MongoDB server. Defaults to the database name provided via
181// the URL path, and "admin" if that's unset.
182//
183//
184// authMechanism=<mechanism>
185//
186// Defines the protocol for credential negotiation. Defaults to "MONGODB-CR",
187// which is the default username/password challenge-response mechanism.
188//
189//
190// gssapiServiceName=<name>
191//
192// Defines the service name to use when authenticating with the GSSAPI
193// mechanism. Defaults to "mongodb".
194//
195// maxPoolSize=<limit>
196//
197// Defines the per-server socket pool limit. Defaults to 4096.
198// See Session.SetPoolLimit for details.
199//
200//
201// Relevant documentation:
202//
203// http://docs.mongodb.org/manual/reference/connection-string/
204//
205func Dial(url string) (*Session, error) {
206 session, err := DialWithTimeout(url, 10*time.Second)
207 if err == nil {
208 session.SetSyncTimeout(1 * time.Minute)
209 session.SetSocketTimeout(1 * time.Minute)
210 }
211 return session, err
212}
213
214// DialWithTimeout works like Dial, but uses timeout as the amount of time to
215// wait for a server to respond when first connecting and also on follow up
216// operations in the session. If timeout is zero, the call may block
217// forever waiting for a connection to be made.
218//
219// See SetSyncTimeout for customizing the timeout for the session.
220func DialWithTimeout(url string, timeout time.Duration) (*Session, error) {
221 info, err := ParseURL(url)
222 if err != nil {
223 return nil, err
224 }
225 info.Timeout = timeout
226 return DialWithInfo(info)
227}
228
229// ParseURL parses a MongoDB URL as accepted by the Dial function and returns
230// a value suitable for providing into DialWithInfo.
231//
232// See Dial for more details on the format of url.
233func ParseURL(url string) (*DialInfo, error) {
234 uinfo, err := extractURL(url)
235 if err != nil {
236 return nil, err
237 }
238 direct := false
239 mechanism := ""
240 service := ""
241 source := ""
242 setName := ""
243 poolLimit := 0
244 for k, v := range uinfo.options {
245 switch k {
246 case "authSource":
247 source = v
248 case "authMechanism":
249 mechanism = v
250 case "gssapiServiceName":
251 service = v
252 case "replicaSet":
253 setName = v
254 case "maxPoolSize":
255 poolLimit, err = strconv.Atoi(v)
256 if err != nil {
257 return nil, errors.New("bad value for maxPoolSize: " + v)
258 }
259 case "connect":
260 if v == "direct" {
261 direct = true
262 break
263 }
264 if v == "replicaSet" {
265 break
266 }
267 fallthrough
268 default:
269 return nil, errors.New("unsupported connection URL option: " + k + "=" + v)
270 }
271 }
272 info := DialInfo{
273 Addrs: uinfo.addrs,
274 Direct: direct,
275 Database: uinfo.db,
276 Username: uinfo.user,
277 Password: uinfo.pass,
278 Mechanism: mechanism,
279 Service: service,
280 Source: source,
281 PoolLimit: poolLimit,
282 ReplicaSetName: setName,
283 }
284 return &info, nil
285}
286
287// DialInfo holds options for establishing a session with a MongoDB cluster.
288// To use a URL, see the Dial function.
289type DialInfo struct {
290 // Addrs holds the addresses for the seed servers.
291 Addrs []string
292
293 // Direct informs whether to establish connections only with the
294 // specified seed servers, or to obtain information for the whole
295 // cluster and establish connections with further servers too.
296 Direct bool
297
298 // Timeout is the amount of time to wait for a server to respond when
299 // first connecting and on follow up operations in the session. If
300 // timeout is zero, the call may block forever waiting for a connection
301 // to be established.
302 Timeout time.Duration
303
304 // FailFast will cause connection and query attempts to fail faster when
305 // the server is unavailable, instead of retrying until the configured
306 // timeout period. Note that an unavailable server may silently drop
307 // packets instead of rejecting them, in which case it's impossible to
308 // distinguish it from a slow server, so the timeout stays relevant.
309 FailFast bool
310
311 // Database is the default database name used when the Session.DB method
312 // is called with an empty name, and is also used during the intial
313 // authentication if Source is unset.
314 Database string
315
316 // ReplicaSetName, if specified, will prevent the obtained session from
317 // communicating with any server which is not part of a replica set
318 // with the given name. The default is to communicate with any server
319 // specified or discovered via the servers contacted.
320 ReplicaSetName string
321
322 // Source is the database used to establish credentials and privileges
323 // with a MongoDB server. Defaults to the value of Database, if that is
324 // set, or "admin" otherwise.
325 Source string
326
327 // Service defines the service name to use when authenticating with the GSSAPI
328 // mechanism. Defaults to "mongodb".
329 Service string
330
331 // ServiceHost defines which hostname to use when authenticating
332 // with the GSSAPI mechanism. If not specified, defaults to the MongoDB
333 // server's address.
334 ServiceHost string
335
336 // Mechanism defines the protocol for credential negotiation.
337 // Defaults to "MONGODB-CR".
338 Mechanism string
339
340 // Username and Password inform the credentials for the initial authentication
341 // done on the database defined by the Source field. See Session.Login.
342 Username string
343 Password string
344
345 // PoolLimit defines the per-server socket pool limit. Defaults to 4096.
346 // See Session.SetPoolLimit for details.
347 PoolLimit int
348
349 // DialServer optionally specifies the dial function for establishing
350 // connections with the MongoDB servers.
351 DialServer func(addr *ServerAddr) (net.Conn, error)
352
353 // WARNING: This field is obsolete. See DialServer above.
354 Dial func(addr net.Addr) (net.Conn, error)
355}
356
357// ServerAddr represents the address for establishing a connection to an
358// individual MongoDB server.
359type ServerAddr struct {
360 str string
361 tcp *net.TCPAddr
362}
363
364// String returns the address that was provided for the server before resolution.
365func (addr *ServerAddr) String() string {
366 return addr.str
367}
368
369// TCPAddr returns the resolved TCP address for the server.
370func (addr *ServerAddr) TCPAddr() *net.TCPAddr {
371 return addr.tcp
372}
373
374// DialWithInfo establishes a new session to the cluster identified by info.
375func DialWithInfo(info *DialInfo) (*Session, error) {
376 addrs := make([]string, len(info.Addrs))
377 for i, addr := range info.Addrs {
378 p := strings.LastIndexAny(addr, "]:")
379 if p == -1 || addr[p] != ':' {
380 // XXX This is untested. The test suite doesn't use the standard port.
381 addr += ":27017"
382 }
383 addrs[i] = addr
384 }
385 cluster := newCluster(addrs, info.Direct, info.FailFast, dialer{info.Dial, info.DialServer}, info.ReplicaSetName)
386 session := newSession(Eventual, cluster, info.Timeout)
387 session.defaultdb = info.Database
388 if session.defaultdb == "" {
389 session.defaultdb = "test"
390 }
391 session.sourcedb = info.Source
392 if session.sourcedb == "" {
393 session.sourcedb = info.Database
394 if session.sourcedb == "" {
395 session.sourcedb = "admin"
396 }
397 }
398 if info.Username != "" {
399 source := session.sourcedb
400 if info.Source == "" &&
401 (info.Mechanism == "GSSAPI" || info.Mechanism == "PLAIN" || info.Mechanism == "MONGODB-X509") {
402 source = "$external"
403 }
404 session.dialCred = &Credential{
405 Username: info.Username,
406 Password: info.Password,
407 Mechanism: info.Mechanism,
408 Service: info.Service,
409 ServiceHost: info.ServiceHost,
410 Source: source,
411 }
412 session.creds = []Credential{*session.dialCred}
413 }
414 if info.PoolLimit > 0 {
415 session.poolLimit = info.PoolLimit
416 }
417 cluster.Release()
418
419 // People get confused when we return a session that is not actually
420 // established to any servers yet (e.g. what if url was wrong). So,
421 // ping the server to ensure there's someone there, and abort if it
422 // fails.
423 if err := session.Ping(); err != nil {
424 session.Close()
425 return nil, err
426 }
427 session.SetMode(Strong, true)
428 return session, nil
429}
430
431func isOptSep(c rune) bool {
432 return c == ';' || c == '&'
433}
434
435type urlInfo struct {
436 addrs []string
437 user string
438 pass string
439 db string
440 options map[string]string
441}
442
443func extractURL(s string) (*urlInfo, error) {
444 if strings.HasPrefix(s, "mongodb://") {
445 s = s[10:]
446 }
447 info := &urlInfo{options: make(map[string]string)}
448 if c := strings.Index(s, "?"); c != -1 {
449 for _, pair := range strings.FieldsFunc(s[c+1:], isOptSep) {
450 l := strings.SplitN(pair, "=", 2)
451 if len(l) != 2 || l[0] == "" || l[1] == "" {
452 return nil, errors.New("connection option must be key=value: " + pair)
453 }
454 info.options[l[0]] = l[1]
455 }
456 s = s[:c]
457 }
458 if c := strings.Index(s, "@"); c != -1 {
459 pair := strings.SplitN(s[:c], ":", 2)
460 if len(pair) > 2 || pair[0] == "" {
461 return nil, errors.New("credentials must be provided as user:pass@host")
462 }
463 var err error
464 info.user, err = url.QueryUnescape(pair[0])
465 if err != nil {
466 return nil, fmt.Errorf("cannot unescape username in URL: %q", pair[0])
467 }
468 if len(pair) > 1 {
469 info.pass, err = url.QueryUnescape(pair[1])
470 if err != nil {
471 return nil, fmt.Errorf("cannot unescape password in URL")
472 }
473 }
474 s = s[c+1:]
475 }
476 if c := strings.Index(s, "/"); c != -1 {
477 info.db = s[c+1:]
478 s = s[:c]
479 }
480 info.addrs = strings.Split(s, ",")
481 return info, nil
482}
483
484func newSession(consistency mode, cluster *mongoCluster, timeout time.Duration) (session *Session) {
485 cluster.Acquire()
486 session = &Session{
487 cluster_: cluster,
488 syncTimeout: timeout,
489 sockTimeout: timeout,
490 poolLimit: 4096,
491 }
492 debugf("New session %p on cluster %p", session, cluster)
493 session.SetMode(consistency, true)
494 session.SetSafe(&Safe{})
495 session.queryConfig.prefetch = defaultPrefetch
496 return session
497}
498
499func copySession(session *Session, keepCreds bool) (s *Session) {
500 cluster := session.cluster()
501 cluster.Acquire()
502 if session.masterSocket != nil {
503 session.masterSocket.Acquire()
504 }
505 if session.slaveSocket != nil {
506 session.slaveSocket.Acquire()
507 }
508 var creds []Credential
509 if keepCreds {
510 creds = make([]Credential, len(session.creds))
511 copy(creds, session.creds)
512 } else if session.dialCred != nil {
513 creds = []Credential{*session.dialCred}
514 }
515 scopy := *session
516 scopy.m = sync.RWMutex{}
517 scopy.creds = creds
518 s = &scopy
519 debugf("New session %p on cluster %p (copy from %p)", s, cluster, session)
520 return s
521}
522
523// LiveServers returns a list of server addresses which are
524// currently known to be alive.
525func (s *Session) LiveServers() (addrs []string) {
526 s.m.RLock()
527 addrs = s.cluster().LiveServers()
528 s.m.RUnlock()
529 return addrs
530}
531
532// DB returns a value representing the named database. If name
533// is empty, the database name provided in the dialed URL is
534// used instead. If that is also empty, "test" is used as a
535// fallback in a way equivalent to the mongo shell.
536//
537// Creating this value is a very lightweight operation, and
538// involves no network communication.
539func (s *Session) DB(name string) *Database {
540 if name == "" {
541 name = s.defaultdb
542 }
543 return &Database{s, name}
544}
545
546// C returns a value representing the named collection.
547//
548// Creating this value is a very lightweight operation, and
549// involves no network communication.
550func (db *Database) C(name string) *Collection {
551 return &Collection{db, name, db.Name + "." + name}
552}
553
554// With returns a copy of db that uses session s.
555func (db *Database) With(s *Session) *Database {
556 newdb := *db
557 newdb.Session = s
558 return &newdb
559}
560
561// With returns a copy of c that uses session s.
562func (c *Collection) With(s *Session) *Collection {
563 newdb := *c.Database
564 newdb.Session = s
565 newc := *c
566 newc.Database = &newdb
567 return &newc
568}
569
570// GridFS returns a GridFS value representing collections in db that
571// follow the standard GridFS specification.
572// The provided prefix (sometimes known as root) will determine which
573// collections to use, and is usually set to "fs" when there is a
574// single GridFS in the database.
575//
576// See the GridFS Create, Open, and OpenId methods for more details.
577//
578// Relevant documentation:
579//
580// http://www.mongodb.org/display/DOCS/GridFS
581// http://www.mongodb.org/display/DOCS/GridFS+Tools
582// http://www.mongodb.org/display/DOCS/GridFS+Specification
583//
584func (db *Database) GridFS(prefix string) *GridFS {
585 return newGridFS(db, prefix)
586}
587
588// Run issues the provided command on the db database and unmarshals
589// its result in the respective argument. The cmd argument may be either
590// a string with the command name itself, in which case an empty document of
591// the form bson.M{cmd: 1} will be used, or it may be a full command document.
592//
593// Note that MongoDB considers the first marshalled key as the command
594// name, so when providing a command with options, it's important to
595// use an ordering-preserving document, such as a struct value or an
596// instance of bson.D. For instance:
597//
598// db.Run(bson.D{{"create", "mycollection"}, {"size", 1024}})
599//
600// For privilleged commands typically run on the "admin" database, see
601// the Run method in the Session type.
602//
603// Relevant documentation:
604//
605// http://www.mongodb.org/display/DOCS/Commands
606// http://www.mongodb.org/display/DOCS/List+of+Database+CommandSkips
607//
608func (db *Database) Run(cmd interface{}, result interface{}) error {
609 socket, err := db.Session.acquireSocket(true)
610 if err != nil {
611 return err
612 }
613 defer socket.Release()
614
615 // This is an optimized form of db.C("$cmd").Find(cmd).One(result).
616 return db.run(socket, cmd, result)
617}
618
619// Credential holds details to authenticate with a MongoDB server.
620type Credential struct {
621 // Username and Password hold the basic details for authentication.
622 // Password is optional with some authentication mechanisms.
623 Username string
624 Password string
625
626 // Source is the database used to establish credentials and privileges
627 // with a MongoDB server. Defaults to the default database provided
628 // during dial, or "admin" if that was unset.
629 Source string
630
631 // Service defines the service name to use when authenticating with the GSSAPI
632 // mechanism. Defaults to "mongodb".
633 Service string
634
635 // ServiceHost defines which hostname to use when authenticating
636 // with the GSSAPI mechanism. If not specified, defaults to the MongoDB
637 // server's address.
638 ServiceHost string
639
640 // Mechanism defines the protocol for credential negotiation.
641 // Defaults to "MONGODB-CR".
642 Mechanism string
643}
644
645// Login authenticates with MongoDB using the provided credential. The
646// authentication is valid for the whole session and will stay valid until
647// Logout is explicitly called for the same database, or the session is
648// closed.
649func (db *Database) Login(user, pass string) error {
650 return db.Session.Login(&Credential{Username: user, Password: pass, Source: db.Name})
651}
652
653// Login authenticates with MongoDB using the provided credential. The
654// authentication is valid for the whole session and will stay valid until
655// Logout is explicitly called for the same database, or the session is
656// closed.
657func (s *Session) Login(cred *Credential) error {
658 socket, err := s.acquireSocket(true)
659 if err != nil {
660 return err
661 }
662 defer socket.Release()
663
664 credCopy := *cred
665 if cred.Source == "" {
666 if cred.Mechanism == "GSSAPI" {
667 credCopy.Source = "$external"
668 } else {
669 credCopy.Source = s.sourcedb
670 }
671 }
672 err = socket.Login(credCopy)
673 if err != nil {
674 return err
675 }
676
677 s.m.Lock()
678 s.creds = append(s.creds, credCopy)
679 s.m.Unlock()
680 return nil
681}
682
683func (s *Session) socketLogin(socket *mongoSocket) error {
684 for _, cred := range s.creds {
685 if err := socket.Login(cred); err != nil {
686 return err
687 }
688 }
689 return nil
690}
691
692// Logout removes any established authentication credentials for the database.
693func (db *Database) Logout() {
694 session := db.Session
695 dbname := db.Name
696 session.m.Lock()
697 found := false
698 for i, cred := range session.creds {
699 if cred.Source == dbname {
700 copy(session.creds[i:], session.creds[i+1:])
701 session.creds = session.creds[:len(session.creds)-1]
702 found = true
703 break
704 }
705 }
706 if found {
707 if session.masterSocket != nil {
708 session.masterSocket.Logout(dbname)
709 }
710 if session.slaveSocket != nil {
711 session.slaveSocket.Logout(dbname)
712 }
713 }
714 session.m.Unlock()
715}
716
717// LogoutAll removes all established authentication credentials for the session.
718func (s *Session) LogoutAll() {
719 s.m.Lock()
720 for _, cred := range s.creds {
721 if s.masterSocket != nil {
722 s.masterSocket.Logout(cred.Source)
723 }
724 if s.slaveSocket != nil {
725 s.slaveSocket.Logout(cred.Source)
726 }
727 }
728 s.creds = s.creds[0:0]
729 s.m.Unlock()
730}
731
732// User represents a MongoDB user.
733//
734// Relevant documentation:
735//
736// http://docs.mongodb.org/manual/reference/privilege-documents/
737// http://docs.mongodb.org/manual/reference/user-privileges/
738//
739type User struct {
740 // Username is how the user identifies itself to the system.
741 Username string `bson:"user"`
742
743 // Password is the plaintext password for the user. If set,
744 // the UpsertUser method will hash it into PasswordHash and
745 // unset it before the user is added to the database.
746 Password string `bson:",omitempty"`
747
748 // PasswordHash is the MD5 hash of Username+":mongo:"+Password.
749 PasswordHash string `bson:"pwd,omitempty"`
750
751 // CustomData holds arbitrary data admins decide to associate
752 // with this user, such as the full name or employee id.
753 CustomData interface{} `bson:"customData,omitempty"`
754
755 // Roles indicates the set of roles the user will be provided.
756 // See the Role constants.
757 Roles []Role `bson:"roles"`
758
759 // OtherDBRoles allows assigning roles in other databases from
760 // user documents inserted in the admin database. This field
761 // only works in the admin database.
762 OtherDBRoles map[string][]Role `bson:"otherDBRoles,omitempty"`
763
764 // UserSource indicates where to look for this user's credentials.
765 // It may be set to a database name, or to "$external" for
766 // consulting an external resource such as Kerberos. UserSource
767 // must not be set if Password or PasswordHash are present.
768 //
769 // WARNING: This setting was only ever supported in MongoDB 2.4,
770 // and is now obsolete.
771 UserSource string `bson:"userSource,omitempty"`
772}
773
774type Role string
775
776const (
777 // Relevant documentation:
778 //
779 // http://docs.mongodb.org/manual/reference/user-privileges/
780 //
781 RoleRoot Role = "root"
782 RoleRead Role = "read"
783 RoleReadAny Role = "readAnyDatabase"
784 RoleReadWrite Role = "readWrite"
785 RoleReadWriteAny Role = "readWriteAnyDatabase"
786 RoleDBAdmin Role = "dbAdmin"
787 RoleDBAdminAny Role = "dbAdminAnyDatabase"
788 RoleUserAdmin Role = "userAdmin"
789 RoleUserAdminAny Role = "userAdminAnyDatabase"
790 RoleClusterAdmin Role = "clusterAdmin"
791)
792
793// UpsertUser updates the authentication credentials and the roles for
794// a MongoDB user within the db database. If the named user doesn't exist
795// it will be created.
796//
797// This method should only be used from MongoDB 2.4 and on. For older
798// MongoDB releases, use the obsolete AddUser method instead.
799//
800// Relevant documentation:
801//
802// http://docs.mongodb.org/manual/reference/user-privileges/
803// http://docs.mongodb.org/manual/reference/privilege-documents/
804//
805func (db *Database) UpsertUser(user *User) error {
806 if user.Username == "" {
807 return fmt.Errorf("user has no Username")
808 }
809 if (user.Password != "" || user.PasswordHash != "") && user.UserSource != "" {
810 return fmt.Errorf("user has both Password/PasswordHash and UserSource set")
811 }
812 if len(user.OtherDBRoles) > 0 && db.Name != "admin" && db.Name != "$external" {
813 return fmt.Errorf("user with OtherDBRoles is only supported in the admin or $external databases")
814 }
815
816 // Attempt to run this using 2.6+ commands.
817 rundb := db
818 if user.UserSource != "" {
819 // Compatibility logic for the userSource field of MongoDB <= 2.4.X
820 rundb = db.Session.DB(user.UserSource)
821 }
822 err := rundb.runUserCmd("updateUser", user)
823 // retry with createUser when isAuthError in order to enable the "localhost exception"
824 if isNotFound(err) || isAuthError(err) {
825 return rundb.runUserCmd("createUser", user)
826 }
827 if !isNoCmd(err) {
828 return err
829 }
830
831 // Command does not exist. Fallback to pre-2.6 behavior.
832 var set, unset bson.D
833 if user.Password != "" {
834 psum := md5.New()
835 psum.Write([]byte(user.Username + ":mongo:" + user.Password))
836 set = append(set, bson.DocElem{"pwd", hex.EncodeToString(psum.Sum(nil))})
837 unset = append(unset, bson.DocElem{"userSource", 1})
838 } else if user.PasswordHash != "" {
839 set = append(set, bson.DocElem{"pwd", user.PasswordHash})
840 unset = append(unset, bson.DocElem{"userSource", 1})
841 }
842 if user.UserSource != "" {
843 set = append(set, bson.DocElem{"userSource", user.UserSource})
844 unset = append(unset, bson.DocElem{"pwd", 1})
845 }
846 if user.Roles != nil || user.OtherDBRoles != nil {
847 set = append(set, bson.DocElem{"roles", user.Roles})
848 if len(user.OtherDBRoles) > 0 {
849 set = append(set, bson.DocElem{"otherDBRoles", user.OtherDBRoles})
850 } else {
851 unset = append(unset, bson.DocElem{"otherDBRoles", 1})
852 }
853 }
854 users := db.C("system.users")
855 err = users.Update(bson.D{{"user", user.Username}}, bson.D{{"$unset", unset}, {"$set", set}})
856 if err == ErrNotFound {
857 set = append(set, bson.DocElem{"user", user.Username})
858 if user.Roles == nil && user.OtherDBRoles == nil {
859 // Roles must be sent, as it's the way MongoDB distinguishes
860 // old-style documents from new-style documents in pre-2.6.
861 set = append(set, bson.DocElem{"roles", user.Roles})
862 }
863 err = users.Insert(set)
864 }
865 return err
866}
867
868func isNoCmd(err error) bool {
869 e, ok := err.(*QueryError)
870 return ok && (e.Code == 59 || e.Code == 13390 || strings.HasPrefix(e.Message, "no such cmd:"))
871}
872
873func isNotFound(err error) bool {
874 e, ok := err.(*QueryError)
875 return ok && e.Code == 11
876}
877
878func isAuthError(err error) bool {
879 e, ok := err.(*QueryError)
880 return ok && e.Code == 13
881}
882
883func (db *Database) runUserCmd(cmdName string, user *User) error {
884 cmd := make(bson.D, 0, 16)
885 cmd = append(cmd, bson.DocElem{cmdName, user.Username})
886 if user.Password != "" {
887 cmd = append(cmd, bson.DocElem{"pwd", user.Password})
888 }
889 var roles []interface{}
890 for _, role := range user.Roles {
891 roles = append(roles, role)
892 }
893 for db, dbroles := range user.OtherDBRoles {
894 for _, role := range dbroles {
895 roles = append(roles, bson.D{{"role", role}, {"db", db}})
896 }
897 }
898 if roles != nil || user.Roles != nil || cmdName == "createUser" {
899 cmd = append(cmd, bson.DocElem{"roles", roles})
900 }
901 err := db.Run(cmd, nil)
902 if !isNoCmd(err) && user.UserSource != "" && (user.UserSource != "$external" || db.Name != "$external") {
903 return fmt.Errorf("MongoDB 2.6+ does not support the UserSource setting")
904 }
905 return err
906}
907
908// AddUser creates or updates the authentication credentials of user within
909// the db database.
910//
911// WARNING: This method is obsolete and should only be used with MongoDB 2.2
912// or earlier. For MongoDB 2.4 and on, use UpsertUser instead.
913func (db *Database) AddUser(username, password string, readOnly bool) error {
914 // Try to emulate the old behavior on 2.6+
915 user := &User{Username: username, Password: password}
916 if db.Name == "admin" {
917 if readOnly {
918 user.Roles = []Role{RoleReadAny}
919 } else {
920 user.Roles = []Role{RoleReadWriteAny}
921 }
922 } else {
923 if readOnly {
924 user.Roles = []Role{RoleRead}
925 } else {
926 user.Roles = []Role{RoleReadWrite}
927 }
928 }
929 err := db.runUserCmd("updateUser", user)
930 if isNotFound(err) {
931 return db.runUserCmd("createUser", user)
932 }
933 if !isNoCmd(err) {
934 return err
935 }
936
937 // Command doesn't exist. Fallback to pre-2.6 behavior.
938 psum := md5.New()
939 psum.Write([]byte(username + ":mongo:" + password))
940 digest := hex.EncodeToString(psum.Sum(nil))
941 c := db.C("system.users")
942 _, err = c.Upsert(bson.M{"user": username}, bson.M{"$set": bson.M{"user": username, "pwd": digest, "readOnly": readOnly}})
943 return err
944}
945
946// RemoveUser removes the authentication credentials of user from the database.
947func (db *Database) RemoveUser(user string) error {
948 err := db.Run(bson.D{{"dropUser", user}}, nil)
949 if isNoCmd(err) {
950 users := db.C("system.users")
951 return users.Remove(bson.M{"user": user})
952 }
953 if isNotFound(err) {
954 return ErrNotFound
955 }
956 return err
957}
958
959type indexSpec struct {
960 Name, NS string
961 Key bson.D
962 Unique bool ",omitempty"
963 DropDups bool "dropDups,omitempty"
964 Background bool ",omitempty"
965 Sparse bool ",omitempty"
966 Bits, Min, Max int ",omitempty"
967 BucketSize float64 "bucketSize,omitempty"
968 ExpireAfter int "expireAfterSeconds,omitempty"
969 Weights bson.D ",omitempty"
970 DefaultLanguage string "default_language,omitempty"
971 LanguageOverride string "language_override,omitempty"
972}
973
974type Index struct {
975 Key []string // Index key fields; prefix name with dash (-) for descending order
976 Unique bool // Prevent two documents from having the same index key
977 DropDups bool // Drop documents with the same index key as a previously indexed one
978 Background bool // Build index in background and return immediately
979 Sparse bool // Only index documents containing the Key fields
980
981 // If ExpireAfter is defined the server will periodically delete
982 // documents with indexed time.Time older than the provided delta.
983 ExpireAfter time.Duration
984
985 // Name holds the stored index name. On creation this field is ignored and the index name
986 // is automatically computed by EnsureIndex based on the index key
987 Name string
988
989 // Properties for spatial indexes.
990 Bits, Min, Max int
991 BucketSize float64
992
993 // Properties for text indexes.
994 DefaultLanguage string
995 LanguageOverride string
996
997 // Weights defines the significance of provided fields relative to other
998 // fields in a text index. The score for a given word in a document is derived
999 // from the weighted sum of the frequency for each of the indexed fields in
1000 // that document. The default field weight is 1.
1001 Weights map[string]int
1002}
1003
1004type indexKeyInfo struct {
1005 name string
1006 key bson.D
1007 weights bson.D
1008}
1009
1010func parseIndexKey(key []string) (*indexKeyInfo, error) {
1011 var keyInfo indexKeyInfo
1012 isText := false
1013 var order interface{}
1014 for _, field := range key {
1015 raw := field
1016 if keyInfo.name != "" {
1017 keyInfo.name += "_"
1018 }
1019 var kind string
1020 if field != "" {
1021 if field[0] == '$' {
1022 if c := strings.Index(field, ":"); c > 1 && c < len(field)-1 {
1023 kind = field[1:c]
1024 field = field[c+1:]
1025 keyInfo.name += field + "_" + kind
1026 } else {
1027 field = "\x00"
1028 }
1029 }
1030 switch field[0] {
1031 case 0:
1032 // Logic above failed. Reset and error.
1033 field = ""
1034 case '@':
1035 order = "2d"
1036 field = field[1:]
1037 // The shell used to render this field as key_ instead of key_2d,
1038 // and mgo followed suit. This has been fixed in recent server
1039 // releases, and mgo followed as well.
1040 keyInfo.name += field + "_2d"
1041 case '-':
1042 order = -1
1043 field = field[1:]
1044 keyInfo.name += field + "_-1"
1045 case '+':
1046 field = field[1:]
1047 fallthrough
1048 default:
1049 if kind == "" {
1050 order = 1
1051 keyInfo.name += field + "_1"
1052 } else {
1053 order = kind
1054 }
1055 }
1056 }
1057 if field == "" || kind != "" && order != kind {
1058 return nil, fmt.Errorf(`invalid index key: want "[$<kind>:][-]<field name>", got %q`, raw)
1059 }
1060 if kind == "text" {
1061 if !isText {
1062 keyInfo.key = append(keyInfo.key, bson.DocElem{"_fts", "text"}, bson.DocElem{"_ftsx", 1})
1063 isText = true
1064 }
1065 keyInfo.weights = append(keyInfo.weights, bson.DocElem{field, 1})
1066 } else {
1067 keyInfo.key = append(keyInfo.key, bson.DocElem{field, order})
1068 }
1069 }
1070 if keyInfo.name == "" {
1071 return nil, errors.New("invalid index key: no fields provided")
1072 }
1073 return &keyInfo, nil
1074}
1075
1076// EnsureIndexKey ensures an index with the given key exists, creating it
1077// if necessary.
1078//
1079// This example:
1080//
1081// err := collection.EnsureIndexKey("a", "b")
1082//
1083// Is equivalent to:
1084//
1085// err := collection.EnsureIndex(mgo.Index{Key: []string{"a", "b"}})
1086//
1087// See the EnsureIndex method for more details.
1088func (c *Collection) EnsureIndexKey(key ...string) error {
1089 return c.EnsureIndex(Index{Key: key})
1090}
1091
1092// EnsureIndex ensures an index with the given key exists, creating it with
1093// the provided parameters if necessary. EnsureIndex does not modify a previously
1094// existent index with a matching key. The old index must be dropped first instead.
1095//
1096// Once EnsureIndex returns successfully, following requests for the same index
1097// will not contact the server unless Collection.DropIndex is used to drop the
1098// same index, or Session.ResetIndexCache is called.
1099//
1100// For example:
1101//
1102// index := Index{
1103// Key: []string{"lastname", "firstname"},
1104// Unique: true,
1105// DropDups: true,
1106// Background: true, // See notes.
1107// Sparse: true,
1108// }
1109// err := collection.EnsureIndex(index)
1110//
1111// The Key value determines which fields compose the index. The index ordering
1112// will be ascending by default. To obtain an index with a descending order,
1113// the field name should be prefixed by a dash (e.g. []string{"-time"}). It can
1114// also be optionally prefixed by an index kind, as in "$text:summary" or
1115// "$2d:-point". The key string format is:
1116//
1117// [$<kind>:][-]<field name>
1118//
1119// If the Unique field is true, the index must necessarily contain only a single
1120// document per Key. With DropDups set to true, documents with the same key
1121// as a previously indexed one will be dropped rather than an error returned.
1122//
1123// If Background is true, other connections will be allowed to proceed using
1124// the collection without the index while it's being built. Note that the
1125// session executing EnsureIndex will be blocked for as long as it takes for
1126// the index to be built.
1127//
1128// If Sparse is true, only documents containing the provided Key fields will be
1129// included in the index. When using a sparse index for sorting, only indexed
1130// documents will be returned.
1131//
1132// If ExpireAfter is non-zero, the server will periodically scan the collection
1133// and remove documents containing an indexed time.Time field with a value
1134// older than ExpireAfter. See the documentation for details:
1135//
1136// http://docs.mongodb.org/manual/tutorial/expire-data
1137//
1138// Other kinds of indexes are also supported through that API. Here is an example:
1139//
1140// index := Index{
1141// Key: []string{"$2d:loc"},
1142// Bits: 26,
1143// }
1144// err := collection.EnsureIndex(index)
1145//
1146// The example above requests the creation of a "2d" index for the "loc" field.
1147//
1148// The 2D index bounds may be changed using the Min and Max attributes of the
1149// Index value. The default bound setting of (-180, 180) is suitable for
1150// latitude/longitude pairs.
1151//
1152// The Bits parameter sets the precision of the 2D geohash values. If not
1153// provided, 26 bits are used, which is roughly equivalent to 1 foot of
1154// precision for the default (-180, 180) index bounds.
1155//
1156// Relevant documentation:
1157//
1158// http://www.mongodb.org/display/DOCS/Indexes
1159// http://www.mongodb.org/display/DOCS/Indexing+Advice+and+FAQ
1160// http://www.mongodb.org/display/DOCS/Indexing+as+a+Background+Operation
1161// http://www.mongodb.org/display/DOCS/Geospatial+Indexing
1162// http://www.mongodb.org/display/DOCS/Multikeys
1163//
1164func (c *Collection) EnsureIndex(index Index) error {
1165 keyInfo, err := parseIndexKey(index.Key)
1166 if err != nil {
1167 return err
1168 }
1169
1170 session := c.Database.Session
1171 cacheKey := c.FullName + "\x00" + keyInfo.name
1172 if session.cluster().HasCachedIndex(cacheKey) {
1173 return nil
1174 }
1175
1176 spec := indexSpec{
1177 Name: keyInfo.name,
1178 NS: c.FullName,
1179 Key: keyInfo.key,
1180 Unique: index.Unique,
1181 DropDups: index.DropDups,
1182 Background: index.Background,
1183 Sparse: index.Sparse,
1184 Bits: index.Bits,
1185 Min: index.Min,
1186 Max: index.Max,
1187 BucketSize: index.BucketSize,
1188 ExpireAfter: int(index.ExpireAfter / time.Second),
1189 Weights: keyInfo.weights,
1190 DefaultLanguage: index.DefaultLanguage,
1191 LanguageOverride: index.LanguageOverride,
1192 }
1193
1194NextField:
1195 for name, weight := range index.Weights {
1196 for i, elem := range spec.Weights {
1197 if elem.Name == name {
1198 spec.Weights[i].Value = weight
1199 continue NextField
1200 }
1201 }
1202 panic("weight provided for field that is not part of index key: " + name)
1203 }
1204
1205 cloned := session.Clone()
1206 defer cloned.Close()
1207 cloned.SetMode(Strong, false)
1208 cloned.EnsureSafe(&Safe{})
1209 db := c.Database.With(cloned)
1210
1211 // Try with a command first.
1212 err = db.Run(bson.D{{"createIndexes", c.Name}, {"indexes", []indexSpec{spec}}}, nil)
1213 if isNoCmd(err) {
1214 // Command not yet supported. Insert into the indexes collection instead.
1215 err = db.C("system.indexes").Insert(&spec)
1216 }
1217 if err == nil {
1218 session.cluster().CacheIndex(cacheKey, true)
1219 }
1220 return err
1221}
1222
1223// DropIndex removes the index with key from the collection.
1224//
1225// The key value determines which fields compose the index. The index ordering
1226// will be ascending by default. To obtain an index with a descending order,
1227// the field name should be prefixed by a dash (e.g. []string{"-time"}).
1228//
1229// For example:
1230//
1231// err := collection.DropIndex("lastname", "firstname")
1232//
1233// See the EnsureIndex method for more details on indexes.
1234func (c *Collection) DropIndex(key ...string) error {
1235 keyInfo, err := parseIndexKey(key)
1236 if err != nil {
1237 return err
1238 }
1239
1240 session := c.Database.Session
1241 cacheKey := c.FullName + "\x00" + keyInfo.name
1242 session.cluster().CacheIndex(cacheKey, false)
1243
1244 session = session.Clone()
1245 defer session.Close()
1246 session.SetMode(Strong, false)
1247
1248 db := c.Database.With(session)
1249 result := struct {
1250 ErrMsg string
1251 Ok bool
1252 }{}
1253 err = db.Run(bson.D{{"dropIndexes", c.Name}, {"index", keyInfo.name}}, &result)
1254 if err != nil {
1255 return err
1256 }
1257 if !result.Ok {
1258 return errors.New(result.ErrMsg)
1259 }
1260 return nil
1261}
1262
1263// Indexes returns a list of all indexes for the collection.
1264//
1265// For example, this snippet would drop all available indexes:
1266//
1267// indexes, err := collection.Indexes()
1268// if err != nil {
1269// return err
1270// }
1271// for _, index := range indexes {
1272// err = collection.DropIndex(index.Key...)
1273// if err != nil {
1274// return err
1275// }
1276// }
1277//
1278// See the EnsureIndex method for more details on indexes.
1279func (c *Collection) Indexes() (indexes []Index, err error) {
1280 // Clone session and set it to Monotonic mode so that the server
1281 // used for the query may be safely obtained afterwards, if
1282 // necessary for iteration when a cursor is received.
1283 session := c.Database.Session
1284 cloned := session.Clone()
1285 cloned.SetMode(Monotonic, false)
1286 defer cloned.Close()
1287
1288 batchSize := int(cloned.queryConfig.op.limit)
1289
1290 // Try with a command.
1291 var result struct {
1292 Indexes []bson.Raw
1293
1294 Cursor struct {
1295 FirstBatch []bson.Raw "firstBatch"
1296 NS string
1297 Id int64
1298 }
1299 }
1300 var iter *Iter
1301 err = c.Database.With(cloned).Run(bson.D{{"listIndexes", c.Name}, {"cursor", bson.D{{"batchSize", batchSize}}}}, &result)
1302 if err == nil {
1303 firstBatch := result.Indexes
1304 if firstBatch == nil {
1305 firstBatch = result.Cursor.FirstBatch
1306 }
1307 ns := strings.SplitN(result.Cursor.NS, ".", 2)
1308 if len(ns) < 2 {
1309 iter = c.With(cloned).NewIter(nil, firstBatch, result.Cursor.Id, nil)
1310 } else {
1311 iter = cloned.DB(ns[0]).C(ns[1]).NewIter(nil, firstBatch, result.Cursor.Id, nil)
1312 }
1313 } else if isNoCmd(err) {
1314 // Command not yet supported. Query the database instead.
1315 iter = c.Database.C("system.indexes").Find(bson.M{"ns": c.FullName}).Iter()
1316 } else {
1317 return nil, err
1318 }
1319
1320 var spec indexSpec
1321 for iter.Next(&spec) {
1322 indexes = append(indexes, indexFromSpec(spec))
1323 }
1324 if err = iter.Close(); err != nil {
1325 return nil, err
1326 }
1327 sort.Sort(indexSlice(indexes))
1328 return indexes, nil
1329}
1330
1331func indexFromSpec(spec indexSpec) Index {
1332 return Index{
1333 Name: spec.Name,
1334 Key: simpleIndexKey(spec.Key),
1335 Unique: spec.Unique,
1336 DropDups: spec.DropDups,
1337 Background: spec.Background,
1338 Sparse: spec.Sparse,
1339 ExpireAfter: time.Duration(spec.ExpireAfter) * time.Second,
1340 }
1341}
1342
1343type indexSlice []Index
1344
1345func (idxs indexSlice) Len() int { return len(idxs) }
1346func (idxs indexSlice) Less(i, j int) bool { return idxs[i].Name < idxs[j].Name }
1347func (idxs indexSlice) Swap(i, j int) { idxs[i], idxs[j] = idxs[j], idxs[i] }
1348
1349func simpleIndexKey(realKey bson.D) (key []string) {
1350 for i := range realKey {
1351 field := realKey[i].Name
1352 vi, ok := realKey[i].Value.(int)
1353 if !ok {
1354 vf, _ := realKey[i].Value.(float64)
1355 vi = int(vf)
1356 }
1357 if vi == 1 {
1358 key = append(key, field)
1359 continue
1360 }
1361 if vi == -1 {
1362 key = append(key, "-"+field)
1363 continue
1364 }
1365 if vs, ok := realKey[i].Value.(string); ok {
1366 key = append(key, "$"+vs+":"+field)
1367 continue
1368 }
1369 panic("Got unknown index key type for field " + field)
1370 }
1371 return
1372}
1373
1374// ResetIndexCache() clears the cache of previously ensured indexes.
1375// Following requests to EnsureIndex will contact the server.
1376func (s *Session) ResetIndexCache() {
1377 s.cluster().ResetIndexCache()
1378}
1379
1380// New creates a new session with the same parameters as the original
1381// session, including consistency, batch size, prefetching, safety mode,
1382// etc. The returned session will use sockets from the pool, so there's
1383// a chance that writes just performed in another session may not yet
1384// be visible.
1385//
1386// Login information from the original session will not be copied over
1387// into the new session unless it was provided through the initial URL
1388// for the Dial function.
1389//
1390// See the Copy and Clone methods.
1391//
1392func (s *Session) New() *Session {
1393 s.m.Lock()
1394 scopy := copySession(s, false)
1395 s.m.Unlock()
1396 scopy.Refresh()
1397 return scopy
1398}
1399
1400// Copy works just like New, but preserves the exact authentication
1401// information from the original session.
1402func (s *Session) Copy() *Session {
1403 s.m.Lock()
1404 scopy := copySession(s, true)
1405 s.m.Unlock()
1406 scopy.Refresh()
1407 return scopy
1408}
1409
1410// Clone works just like Copy, but also reuses the same socket as the original
1411// session, in case it had already reserved one due to its consistency
1412// guarantees. This behavior ensures that writes performed in the old session
1413// are necessarily observed when using the new session, as long as it was a
1414// strong or monotonic session. That said, it also means that long operations
1415// may cause other goroutines using the original session to wait.
1416func (s *Session) Clone() *Session {
1417 s.m.Lock()
1418 scopy := copySession(s, true)
1419 s.m.Unlock()
1420 return scopy
1421}
1422
1423// Close terminates the session. It's a runtime error to use a session
1424// after it has been closed.
1425func (s *Session) Close() {
1426 s.m.Lock()
1427 if s.cluster_ != nil {
1428 debugf("Closing session %p", s)
1429 s.unsetSocket()
1430 s.cluster_.Release()
1431 s.cluster_ = nil
1432 }
1433 s.m.Unlock()
1434}
1435
1436func (s *Session) cluster() *mongoCluster {
1437 if s.cluster_ == nil {
1438 panic("Session already closed")
1439 }
1440 return s.cluster_
1441}
1442
1443// Refresh puts back any reserved sockets in use and restarts the consistency
1444// guarantees according to the current consistency setting for the session.
1445func (s *Session) Refresh() {
1446 s.m.Lock()
1447 s.slaveOk = s.consistency != Strong
1448 s.unsetSocket()
1449 s.m.Unlock()
1450}
1451
1452// SetMode changes the consistency mode for the session.
1453//
1454// In the Strong consistency mode reads and writes will always be made to
1455// the primary server using a unique connection so that reads and writes are
1456// fully consistent, ordered, and observing the most up-to-date data.
1457// This offers the least benefits in terms of distributing load, but the
1458// most guarantees. See also Monotonic and Eventual.
1459//
1460// In the Monotonic consistency mode reads may not be entirely up-to-date,
1461// but they will always see the history of changes moving forward, the data
1462// read will be consistent across sequential queries in the same session,
1463// and modifications made within the session will be observed in following
1464// queries (read-your-writes).
1465//
1466// In practice, the Monotonic mode is obtained by performing initial reads
1467// on a unique connection to an arbitrary secondary, if one is available,
1468// and once the first write happens, the session connection is switched over
1469// to the primary server. This manages to distribute some of the reading
1470// load with secondaries, while maintaining some useful guarantees.
1471//
1472// In the Eventual consistency mode reads will be made to any secondary in the
1473// cluster, if one is available, and sequential reads will not necessarily
1474// be made with the same connection. This means that data may be observed
1475// out of order. Writes will of course be issued to the primary, but
1476// independent writes in the same Eventual session may also be made with
1477// independent connections, so there are also no guarantees in terms of
1478// write ordering (no read-your-writes guarantees either).
1479//
1480// The Eventual mode is the fastest and most resource-friendly, but is
1481// also the one offering the least guarantees about ordering of the data
1482// read and written.
1483//
1484// If refresh is true, in addition to ensuring the session is in the given
1485// consistency mode, the consistency guarantees will also be reset (e.g.
1486// a Monotonic session will be allowed to read from secondaries again).
1487// This is equivalent to calling the Refresh function.
1488//
1489// Shifting between Monotonic and Strong modes will keep a previously
1490// reserved connection for the session unless refresh is true or the
1491// connection is unsuitable (to a secondary server in a Strong session).
1492func (s *Session) SetMode(consistency mode, refresh bool) {
1493 s.m.Lock()
1494 debugf("Session %p: setting mode %d with refresh=%v (master=%p, slave=%p)", s, consistency, refresh, s.masterSocket, s.slaveSocket)
1495 s.consistency = consistency
1496 if refresh {
1497 s.slaveOk = s.consistency != Strong
1498 s.unsetSocket()
1499 } else if s.consistency == Strong {
1500 s.slaveOk = false
1501 } else if s.masterSocket == nil {
1502 s.slaveOk = true
1503 }
1504 s.m.Unlock()
1505}
1506
1507// Mode returns the current consistency mode for the session.
1508func (s *Session) Mode() mode {
1509 s.m.RLock()
1510 mode := s.consistency
1511 s.m.RUnlock()
1512 return mode
1513}
1514
1515// SetSyncTimeout sets the amount of time an operation with this session
1516// will wait before returning an error in case a connection to a usable
1517// server can't be established. Set it to zero to wait forever. The
1518// default value is 7 seconds.
1519func (s *Session) SetSyncTimeout(d time.Duration) {
1520 s.m.Lock()
1521 s.syncTimeout = d
1522 s.m.Unlock()
1523}
1524
1525// SetSocketTimeout sets the amount of time to wait for a non-responding
1526// socket to the database before it is forcefully closed.
1527func (s *Session) SetSocketTimeout(d time.Duration) {
1528 s.m.Lock()
1529 s.sockTimeout = d
1530 if s.masterSocket != nil {
1531 s.masterSocket.SetTimeout(d)
1532 }
1533 if s.slaveSocket != nil {
1534 s.slaveSocket.SetTimeout(d)
1535 }
1536 s.m.Unlock()
1537}
1538
1539// SetCursorTimeout changes the standard timeout period that the server
1540// enforces on created cursors. The only supported value right now is
1541// 0, which disables the timeout. The standard server timeout is 10 minutes.
1542func (s *Session) SetCursorTimeout(d time.Duration) {
1543 s.m.Lock()
1544 if d == 0 {
1545 s.queryConfig.op.flags |= flagNoCursorTimeout
1546 } else {
1547 panic("SetCursorTimeout: only 0 (disable timeout) supported for now")
1548 }
1549 s.m.Unlock()
1550}
1551
1552// SetPoolLimit sets the maximum number of sockets in use in a single server
1553// before this session will block waiting for a socket to be available.
1554// The default limit is 4096.
1555//
1556// This limit must be set to cover more than any expected workload of the
1557// application. It is a bad practice and an unsupported use case to use the
1558// database driver to define the concurrency limit of an application. Prevent
1559// such concurrency "at the door" instead, by properly restricting the amount
1560// of used resources and number of goroutines before they are created.
1561func (s *Session) SetPoolLimit(limit int) {
1562 s.m.Lock()
1563 s.poolLimit = limit
1564 s.m.Unlock()
1565}
1566
1567// SetBatch sets the default batch size used when fetching documents from the
1568// database. It's possible to change this setting on a per-query basis as
1569// well, using the Query.Batch method.
1570//
1571// The default batch size is defined by the database itself. As of this
1572// writing, MongoDB will use an initial size of min(100 docs, 4MB) on the
1573// first batch, and 4MB on remaining ones.
1574func (s *Session) SetBatch(n int) {
1575 if n == 1 {
1576 // Server interprets 1 as -1 and closes the cursor (!?)
1577 n = 2
1578 }
1579 s.m.Lock()
1580 s.queryConfig.op.limit = int32(n)
1581 s.m.Unlock()
1582}
1583
1584// SetPrefetch sets the default point at which the next batch of results will be
1585// requested. When there are p*batch_size remaining documents cached in an
1586// Iter, the next batch will be requested in background. For instance, when
1587// using this:
1588//
1589// session.SetBatch(200)
1590// session.SetPrefetch(0.25)
1591//
1592// and there are only 50 documents cached in the Iter to be processed, the
1593// next batch of 200 will be requested. It's possible to change this setting on
1594// a per-query basis as well, using the Prefetch method of Query.
1595//
1596// The default prefetch value is 0.25.
1597func (s *Session) SetPrefetch(p float64) {
1598 s.m.Lock()
1599 s.queryConfig.prefetch = p
1600 s.m.Unlock()
1601}
1602
1603// See SetSafe for details on the Safe type.
1604type Safe struct {
1605 W int // Min # of servers to ack before success
1606 WMode string // Write mode for MongoDB 2.0+ (e.g. "majority")
1607 WTimeout int // Milliseconds to wait for W before timing out
1608 FSync bool // Should servers sync to disk before returning success
1609 J bool // Wait for next group commit if journaling; no effect otherwise
1610}
1611
1612// Safe returns the current safety mode for the session.
1613func (s *Session) Safe() (safe *Safe) {
1614 s.m.Lock()
1615 defer s.m.Unlock()
1616 if s.safeOp != nil {
1617 cmd := s.safeOp.query.(*getLastError)
1618 safe = &Safe{WTimeout: cmd.WTimeout, FSync: cmd.FSync, J: cmd.J}
1619 switch w := cmd.W.(type) {
1620 case string:
1621 safe.WMode = w
1622 case int:
1623 safe.W = w
1624 }
1625 }
1626 return
1627}
1628
1629// SetSafe changes the session safety mode.
1630//
1631// If the safe parameter is nil, the session is put in unsafe mode, and writes
1632// become fire-and-forget, without error checking. The unsafe mode is faster
1633// since operations won't hold on waiting for a confirmation.
1634//
1635// If the safe parameter is not nil, any changing query (insert, update, ...)
1636// will be followed by a getLastError command with the specified parameters,
1637// to ensure the request was correctly processed.
1638//
1639// The safe.W parameter determines how many servers should confirm a write
1640// before the operation is considered successful. If set to 0 or 1, the
1641// command will return as soon as the primary is done with the request.
1642// If safe.WTimeout is greater than zero, it determines how many milliseconds
1643// to wait for the safe.W servers to respond before returning an error.
1644//
1645// Starting with MongoDB 2.0.0 the safe.WMode parameter can be used instead
1646// of W to request for richer semantics. If set to "majority" the server will
1647// wait for a majority of members from the replica set to respond before
1648// returning. Custom modes may also be defined within the server to create
1649// very detailed placement schemas. See the data awareness documentation in
1650// the links below for more details (note that MongoDB internally reuses the
1651// "w" field name for WMode).
1652//
1653// If safe.FSync is true and journaling is disabled, the servers will be
1654// forced to sync all files to disk immediately before returning. If the
1655// same option is true but journaling is enabled, the server will instead
1656// await for the next group commit before returning.
1657//
1658// Since MongoDB 2.0.0, the safe.J option can also be used instead of FSync
1659// to force the server to wait for a group commit in case journaling is
1660// enabled. The option has no effect if the server has journaling disabled.
1661//
1662// For example, the following statement will make the session check for
1663// errors, without imposing further constraints:
1664//
1665// session.SetSafe(&mgo.Safe{})
1666//
1667// The following statement will force the server to wait for a majority of
1668// members of a replica set to return (MongoDB 2.0+ only):
1669//
1670// session.SetSafe(&mgo.Safe{WMode: "majority"})
1671//
1672// The following statement, on the other hand, ensures that at least two
1673// servers have flushed the change to disk before confirming the success
1674// of operations:
1675//
1676// session.EnsureSafe(&mgo.Safe{W: 2, FSync: true})
1677//
1678// The following statement, on the other hand, disables the verification
1679// of errors entirely:
1680//
1681// session.SetSafe(nil)
1682//
1683// See also the EnsureSafe method.
1684//
1685// Relevant documentation:
1686//
1687// http://www.mongodb.org/display/DOCS/getLastError+Command
1688// http://www.mongodb.org/display/DOCS/Verifying+Propagation+of+Writes+with+getLastError
1689// http://www.mongodb.org/display/DOCS/Data+Center+Awareness
1690//
1691func (s *Session) SetSafe(safe *Safe) {
1692 s.m.Lock()
1693 s.safeOp = nil
1694 s.ensureSafe(safe)
1695 s.m.Unlock()
1696}
1697
1698// EnsureSafe compares the provided safety parameters with the ones
1699// currently in use by the session and picks the most conservative
1700// choice for each setting.
1701//
1702// That is:
1703//
1704// - safe.WMode is always used if set.
1705// - safe.W is used if larger than the current W and WMode is empty.
1706// - safe.FSync is always used if true.
1707// - safe.J is used if FSync is false.
1708// - safe.WTimeout is used if set and smaller than the current WTimeout.
1709//
1710// For example, the following statement will ensure the session is
1711// at least checking for errors, without enforcing further constraints.
1712// If a more conservative SetSafe or EnsureSafe call was previously done,
1713// the following call will be ignored.
1714//
1715// session.EnsureSafe(&mgo.Safe{})
1716//
1717// See also the SetSafe method for details on what each option means.
1718//
1719// Relevant documentation:
1720//
1721// http://www.mongodb.org/display/DOCS/getLastError+Command
1722// http://www.mongodb.org/display/DOCS/Verifying+Propagation+of+Writes+with+getLastError
1723// http://www.mongodb.org/display/DOCS/Data+Center+Awareness
1724//
1725func (s *Session) EnsureSafe(safe *Safe) {
1726 s.m.Lock()
1727 s.ensureSafe(safe)
1728 s.m.Unlock()
1729}
1730
1731func (s *Session) ensureSafe(safe *Safe) {
1732 if safe == nil {
1733 return
1734 }
1735
1736 var w interface{}
1737 if safe.WMode != "" {
1738 w = safe.WMode
1739 } else if safe.W > 0 {
1740 w = safe.W
1741 }
1742
1743 var cmd getLastError
1744 if s.safeOp == nil {
1745 cmd = getLastError{1, w, safe.WTimeout, safe.FSync, safe.J}
1746 } else {
1747 // Copy. We don't want to mutate the existing query.
1748 cmd = *(s.safeOp.query.(*getLastError))
1749 if cmd.W == nil {
1750 cmd.W = w
1751 } else if safe.WMode != "" {
1752 cmd.W = safe.WMode
1753 } else if i, ok := cmd.W.(int); ok && safe.W > i {
1754 cmd.W = safe.W
1755 }
1756 if safe.WTimeout > 0 && safe.WTimeout < cmd.WTimeout {
1757 cmd.WTimeout = safe.WTimeout
1758 }
1759 if safe.FSync {
1760 cmd.FSync = true
1761 cmd.J = false
1762 } else if safe.J && !cmd.FSync {
1763 cmd.J = true
1764 }
1765 }
1766 s.safeOp = &queryOp{
1767 query: &cmd,
1768 collection: "admin.$cmd",
1769 limit: -1,
1770 }
1771}
1772
1773// Run issues the provided command on the "admin" database and
1774// and unmarshals its result in the respective argument. The cmd
1775// argument may be either a string with the command name itself, in
1776// which case an empty document of the form bson.M{cmd: 1} will be used,
1777// or it may be a full command document.
1778//
1779// Note that MongoDB considers the first marshalled key as the command
1780// name, so when providing a command with options, it's important to
1781// use an ordering-preserving document, such as a struct value or an
1782// instance of bson.D. For instance:
1783//
1784// db.Run(bson.D{{"create", "mycollection"}, {"size", 1024}})
1785//
1786// For commands on arbitrary databases, see the Run method in
1787// the Database type.
1788//
1789// Relevant documentation:
1790//
1791// http://www.mongodb.org/display/DOCS/Commands
1792// http://www.mongodb.org/display/DOCS/List+of+Database+CommandSkips
1793//
1794func (s *Session) Run(cmd interface{}, result interface{}) error {
1795 return s.DB("admin").Run(cmd, result)
1796}
1797
1798// SelectServers restricts communication to servers configured with the
1799// given tags. For example, the following statement restricts servers
1800// used for reading operations to those with both tag "disk" set to
1801// "ssd" and tag "rack" set to 1:
1802//
1803// session.SelectSlaves(bson.D{{"disk", "ssd"}, {"rack", 1}})
1804//
1805// Multiple sets of tags may be provided, in which case the used server
1806// must match all tags within any one set.
1807//
1808// If a connection was previously assigned to the session due to the
1809// current session mode (see Session.SetMode), the tag selection will
1810// only be enforced after the session is refreshed.
1811//
1812// Relevant documentation:
1813//
1814// http://docs.mongodb.org/manual/tutorial/configure-replica-set-tag-sets
1815//
1816func (s *Session) SelectServers(tags ...bson.D) {
1817 s.m.Lock()
1818 s.queryConfig.op.serverTags = tags
1819 s.m.Unlock()
1820}
1821
1822// Ping runs a trivial ping command just to get in touch with the server.
1823func (s *Session) Ping() error {
1824 return s.Run("ping", nil)
1825}
1826
1827// Fsync flushes in-memory writes to disk on the server the session
1828// is established with. If async is true, the call returns immediately,
1829// otherwise it returns after the flush has been made.
1830func (s *Session) Fsync(async bool) error {
1831 return s.Run(bson.D{{"fsync", 1}, {"async", async}}, nil)
1832}
1833
1834// FsyncLock locks all writes in the specific server the session is
1835// established with and returns. Any writes attempted to the server
1836// after it is successfully locked will block until FsyncUnlock is
1837// called for the same server.
1838//
1839// This method works on secondaries as well, preventing the oplog from
1840// being flushed while the server is locked, but since only the server
1841// connected to is locked, for locking specific secondaries it may be
1842// necessary to establish a connection directly to the secondary (see
1843// Dial's connect=direct option).
1844//
1845// As an important caveat, note that once a write is attempted and
1846// blocks, follow up reads will block as well due to the way the
1847// lock is internally implemented in the server. More details at:
1848//
1849// https://jira.mongodb.org/browse/SERVER-4243
1850//
1851// FsyncLock is often used for performing consistent backups of
1852// the database files on disk.
1853//
1854// Relevant documentation:
1855//
1856// http://www.mongodb.org/display/DOCS/fsync+Command
1857// http://www.mongodb.org/display/DOCS/Backups
1858//
1859func (s *Session) FsyncLock() error {
1860 return s.Run(bson.D{{"fsync", 1}, {"lock", true}}, nil)
1861}
1862
1863// FsyncUnlock releases the server for writes. See FsyncLock for details.
1864func (s *Session) FsyncUnlock() error {
1865 return s.DB("admin").C("$cmd.sys.unlock").Find(nil).One(nil) // WTF?
1866}
1867
1868// Find prepares a query using the provided document. The document may be a
1869// map or a struct value capable of being marshalled with bson. The map
1870// may be a generic one using interface{} for its key and/or values, such as
1871// bson.M, or it may be a properly typed map. Providing nil as the document
1872// is equivalent to providing an empty document such as bson.M{}.
1873//
1874// Further details of the query may be tweaked using the resulting Query value,
1875// and then executed to retrieve results using methods such as One, For,
1876// Iter, or Tail.
1877//
1878// In case the resulting document includes a field named $err or errmsg, which
1879// are standard ways for MongoDB to return query errors, the returned err will
1880// be set to a *QueryError value including the Err message and the Code. In
1881// those cases, the result argument is still unmarshalled into with the
1882// received document so that any other custom values may be obtained if
1883// desired.
1884//
1885// Relevant documentation:
1886//
1887// http://www.mongodb.org/display/DOCS/Querying
1888// http://www.mongodb.org/display/DOCS/Advanced+Queries
1889//
1890func (c *Collection) Find(query interface{}) *Query {
1891 session := c.Database.Session
1892 session.m.RLock()
1893 q := &Query{session: session, query: session.queryConfig}
1894 session.m.RUnlock()
1895 q.op.query = query
1896 q.op.collection = c.FullName
1897 return q
1898}
1899
1900type repairCmd struct {
1901 RepairCursor string `bson:"repairCursor"`
1902 Cursor *repairCmdCursor ",omitempty"
1903}
1904
1905type repairCmdCursor struct {
1906 BatchSize int `bson:"batchSize,omitempty"`
1907}
1908
1909// Repair returns an iterator that goes over all recovered documents in the
1910// collection, in a best-effort manner. This is most useful when there are
1911// damaged data files. Multiple copies of the same document may be returned
1912// by the iterator.
1913//
1914// Repair is supported in MongoDB 2.7.8 and later.
1915func (c *Collection) Repair() *Iter {
1916 // Clone session and set it to Monotonic mode so that the server
1917 // used for the query may be safely obtained afterwards, if
1918 // necessary for iteration when a cursor is received.
1919 session := c.Database.Session
1920 cloned := session.Clone()
1921 cloned.SetMode(Monotonic, false)
1922 defer cloned.Close()
1923
1924 batchSize := int(cloned.queryConfig.op.limit)
1925
1926 var result struct {
1927 Cursor struct {
1928 FirstBatch []bson.Raw "firstBatch"
1929 Id int64
1930 }
1931 }
1932
1933 cmd := repairCmd{
1934 RepairCursor: c.Name,
1935 Cursor: &repairCmdCursor{batchSize},
1936 }
1937
1938 clonedc := c.With(cloned)
1939 err := clonedc.Database.Run(cmd, &result)
1940 return clonedc.NewIter(session, result.Cursor.FirstBatch, result.Cursor.Id, err)
1941}
1942
1943// FindId is a convenience helper equivalent to:
1944//
1945// query := collection.Find(bson.M{"_id": id})
1946//
1947// See the Find method for more details.
1948func (c *Collection) FindId(id interface{}) *Query {
1949 return c.Find(bson.D{{"_id", id}})
1950}
1951
1952type Pipe struct {
1953 session *Session
1954 collection *Collection
1955 pipeline interface{}
1956 allowDisk bool
1957 batchSize int
1958}
1959
1960type pipeCmd struct {
1961 Aggregate string
1962 Pipeline interface{}
1963 Cursor *pipeCmdCursor ",omitempty"
1964 Explain bool ",omitempty"
1965 AllowDisk bool "allowDiskUse,omitempty"
1966}
1967
1968type pipeCmdCursor struct {
1969 BatchSize int `bson:"batchSize,omitempty"`
1970}
1971
1972// Pipe prepares a pipeline to aggregate. The pipeline document
1973// must be a slice built in terms of the aggregation framework language.
1974//
1975// For example:
1976//
1977// pipe := collection.Pipe([]bson.M{{"$match": bson.M{"name": "Otavio"}}})
1978// iter := pipe.Iter()
1979//
1980// Relevant documentation:
1981//
1982// http://docs.mongodb.org/manual/reference/aggregation
1983// http://docs.mongodb.org/manual/applications/aggregation
1984// http://docs.mongodb.org/manual/tutorial/aggregation-examples
1985//
1986func (c *Collection) Pipe(pipeline interface{}) *Pipe {
1987 session := c.Database.Session
1988 session.m.RLock()
1989 batchSize := int(session.queryConfig.op.limit)
1990 session.m.RUnlock()
1991 return &Pipe{
1992 session: session,
1993 collection: c,
1994 pipeline: pipeline,
1995 batchSize: batchSize,
1996 }
1997}
1998
1999// Iter executes the pipeline and returns an iterator capable of going
2000// over all the generated results.
2001func (p *Pipe) Iter() *Iter {
2002 // Clone session and set it to Monotonic mode so that the server
2003 // used for the query may be safely obtained afterwards, if
2004 // necessary for iteration when a cursor is received.
2005 cloned := p.session.Clone()
2006 cloned.SetMode(Monotonic, false)
2007 defer cloned.Close()
2008 c := p.collection.With(cloned)
2009
2010 var result struct {
2011 // 2.4, no cursors.
2012 Result []bson.Raw
2013
2014 // 2.6+, with cursors.
2015 Cursor struct {
2016 FirstBatch []bson.Raw "firstBatch"
2017 Id int64
2018 }
2019 }
2020
2021 cmd := pipeCmd{
2022 Aggregate: c.Name,
2023 Pipeline: p.pipeline,
2024 AllowDisk: p.allowDisk,
2025 Cursor: &pipeCmdCursor{p.batchSize},
2026 }
2027 err := c.Database.Run(cmd, &result)
2028 if e, ok := err.(*QueryError); ok && e.Message == `unrecognized field "cursor` {
2029 cmd.Cursor = nil
2030 cmd.AllowDisk = false
2031 err = c.Database.Run(cmd, &result)
2032 }
2033 firstBatch := result.Result
2034 if firstBatch == nil {
2035 firstBatch = result.Cursor.FirstBatch
2036 }
2037 return c.NewIter(p.session, firstBatch, result.Cursor.Id, err)
2038}
2039
2040// NewIter returns a newly created iterator with the provided parameters.
2041// Using this method is not recommended unless the desired functionality
2042// is not yet exposed via a more convenient interface (Find, Pipe, etc).
2043//
2044// The optional session parameter associates the lifetime of the returned
2045// iterator to an arbitrary session. If nil, the iterator will be bound to
2046// c's session.
2047//
2048// Documents in firstBatch will be individually provided by the returned
2049// iterator before documents from cursorId are made available. If cursorId
2050// is zero, only the documents in firstBatch are provided.
2051//
2052// If err is not nil, the iterator's Err method will report it after
2053// exhausting documents in firstBatch.
2054//
2055// NewIter must be called right after the cursor id is obtained, and must not
2056// be called on a collection in Eventual mode, because the cursor id is
2057// associated with the specific server that returned it. The provided session
2058// parameter may be in any mode or state, though.
2059//
2060func (c *Collection) NewIter(session *Session, firstBatch []bson.Raw, cursorId int64, err error) *Iter {
2061 var server *mongoServer
2062 csession := c.Database.Session
2063 csession.m.RLock()
2064 socket := csession.masterSocket
2065 if socket == nil {
2066 socket = csession.slaveSocket
2067 }
2068 if socket != nil {
2069 server = socket.Server()
2070 }
2071 csession.m.RUnlock()
2072
2073 if server == nil {
2074 if csession.Mode() == Eventual {
2075 panic("Collection.NewIter called in Eventual mode")
2076 }
2077 if err == nil {
2078 err = errors.New("server not available")
2079 }
2080 }
2081
2082 if session == nil {
2083 session = csession
2084 }
2085
2086 iter := &Iter{
2087 session: session,
2088 server: server,
2089 timeout: -1,
2090 err: err,
2091 }
2092 iter.gotReply.L = &iter.m
2093 for _, doc := range firstBatch {
2094 iter.docData.Push(doc.Data)
2095 }
2096 if cursorId != 0 {
2097 iter.op.cursorId = cursorId
2098 iter.op.collection = c.FullName
2099 iter.op.replyFunc = iter.replyFunc()
2100 }
2101 return iter
2102}
2103
2104// All works like Iter.All.
2105func (p *Pipe) All(result interface{}) error {
2106 return p.Iter().All(result)
2107}
2108
2109// One executes the pipeline and unmarshals the first item from the
2110// result set into the result parameter.
2111// It returns ErrNotFound if no items are generated by the pipeline.
2112func (p *Pipe) One(result interface{}) error {
2113 iter := p.Iter()
2114 if iter.Next(result) {
2115 return nil
2116 }
2117 if err := iter.Err(); err != nil {
2118 return err
2119 }
2120 return ErrNotFound
2121}
2122
2123// Explain returns a number of details about how the MongoDB server would
2124// execute the requested pipeline, such as the number of objects examined,
2125// the number of times the read lock was yielded to allow writes to go in,
2126// and so on.
2127//
2128// For example:
2129//
2130// var m bson.M
2131// err := collection.Pipe(pipeline).Explain(&m)
2132// if err == nil {
2133// fmt.Printf("Explain: %#v\n", m)
2134// }
2135//
2136func (p *Pipe) Explain(result interface{}) error {
2137 c := p.collection
2138 cmd := pipeCmd{
2139 Aggregate: c.Name,
2140 Pipeline: p.pipeline,
2141 AllowDisk: p.allowDisk,
2142 Explain: true,
2143 }
2144 return c.Database.Run(cmd, result)
2145}
2146
2147// AllowDiskUse enables writing to the "<dbpath>/_tmp" server directory so
2148// that aggregation pipelines do not have to be held entirely in memory.
2149func (p *Pipe) AllowDiskUse() *Pipe {
2150 p.allowDisk = true
2151 return p
2152}
2153
2154// Batch sets the batch size used when fetching documents from the database.
2155// It's possible to change this setting on a per-session basis as well, using
2156// the Batch method of Session.
2157//
2158// The default batch size is defined by the database server.
2159func (p *Pipe) Batch(n int) *Pipe {
2160 p.batchSize = n
2161 return p
2162}
2163
2164type LastError struct {
2165 Err string
2166 Code, N, Waited int
2167 FSyncFiles int `bson:"fsyncFiles"`
2168 WTimeout bool
2169 UpdatedExisting bool `bson:"updatedExisting"`
2170 UpsertedId interface{} `bson:"upserted"`
2171}
2172
2173func (err *LastError) Error() string {
2174 return err.Err
2175}
2176
2177type queryError struct {
2178 Err string "$err"
2179 ErrMsg string
2180 Assertion string
2181 Code int
2182 AssertionCode int "assertionCode"
2183 LastError *LastError "lastErrorObject"
2184}
2185
2186type QueryError struct {
2187 Code int
2188 Message string
2189 Assertion bool
2190}
2191
2192func (err *QueryError) Error() string {
2193 return err.Message
2194}
2195
2196// IsDup returns whether err informs of a duplicate key error because
2197// a primary key index or a secondary unique index already has an entry
2198// with the given value.
2199func IsDup(err error) bool {
2200 // Besides being handy, helps with MongoDB bugs SERVER-7164 and SERVER-11493.
2201 // What follows makes me sad. Hopefully conventions will be more clear over time.
2202 switch e := err.(type) {
2203 case *LastError:
2204 return e.Code == 11000 || e.Code == 11001 || e.Code == 12582 || e.Code == 16460 && strings.Contains(e.Err, " E11000 ")
2205 case *QueryError:
2206 return e.Code == 11000 || e.Code == 11001 || e.Code == 12582
2207 }
2208 return false
2209}
2210
2211// Insert inserts one or more documents in the respective collection. In
2212// case the session is in safe mode (see the SetSafe method) and an error
2213// happens while inserting the provided documents, the returned error will
2214// be of type *LastError.
2215func (c *Collection) Insert(docs ...interface{}) error {
2216 _, err := c.writeQuery(&insertOp{c.FullName, docs, 0})
2217 return err
2218}
2219
2220// Update finds a single document matching the provided selector document
2221// and modifies it according to the update document.
2222// If the session is in safe mode (see SetSafe) a ErrNotFound error is
2223// returned if a document isn't found, or a value of type *LastError
2224// when some other error is detected.
2225//
2226// Relevant documentation:
2227//
2228// http://www.mongodb.org/display/DOCS/Updating
2229// http://www.mongodb.org/display/DOCS/Atomic+Operations
2230//
2231func (c *Collection) Update(selector interface{}, update interface{}) error {
2232 lerr, err := c.writeQuery(&updateOp{c.FullName, selector, update, 0})
2233 if err == nil && lerr != nil && !lerr.UpdatedExisting {
2234 return ErrNotFound
2235 }
2236 return err
2237}
2238
2239// UpdateId is a convenience helper equivalent to:
2240//
2241// err := collection.Update(bson.M{"_id": id}, update)
2242//
2243// See the Update method for more details.
2244func (c *Collection) UpdateId(id interface{}, update interface{}) error {
2245 return c.Update(bson.D{{"_id", id}}, update)
2246}
2247
2248// ChangeInfo holds details about the outcome of an update operation.
2249type ChangeInfo struct {
2250 Updated int // Number of existing documents updated
2251 Removed int // Number of documents removed
2252 UpsertedId interface{} // Upserted _id field, when not explicitly provided
2253}
2254
2255// UpdateAll finds all documents matching the provided selector document
2256// and modifies them according to the update document.
2257// If the session is in safe mode (see SetSafe) details of the executed
2258// operation are returned in info or an error of type *LastError when
2259// some problem is detected. It is not an error for the update to not be
2260// applied on any documents because the selector doesn't match.
2261//
2262// Relevant documentation:
2263//
2264// http://www.mongodb.org/display/DOCS/Updating
2265// http://www.mongodb.org/display/DOCS/Atomic+Operations
2266//
2267func (c *Collection) UpdateAll(selector interface{}, update interface{}) (info *ChangeInfo, err error) {
2268 lerr, err := c.writeQuery(&updateOp{c.FullName, selector, update, 2})
2269 if err == nil && lerr != nil {
2270 info = &ChangeInfo{Updated: lerr.N}
2271 }
2272 return info, err
2273}
2274
2275// Upsert finds a single document matching the provided selector document
2276// and modifies it according to the update document. If no document matching
2277// the selector is found, the update document is applied to the selector
2278// document and the result is inserted in the collection.
2279// If the session is in safe mode (see SetSafe) details of the executed
2280// operation are returned in info, or an error of type *LastError when
2281// some problem is detected.
2282//
2283// Relevant documentation:
2284//
2285// http://www.mongodb.org/display/DOCS/Updating
2286// http://www.mongodb.org/display/DOCS/Atomic+Operations
2287//
2288func (c *Collection) Upsert(selector interface{}, update interface{}) (info *ChangeInfo, err error) {
2289 lerr, err := c.writeQuery(&updateOp{c.FullName, selector, update, 1})
2290 if err == nil && lerr != nil {
2291 info = &ChangeInfo{}
2292 if lerr.UpdatedExisting {
2293 info.Updated = lerr.N
2294 } else {
2295 info.UpsertedId = lerr.UpsertedId
2296 }
2297 }
2298 return info, err
2299}
2300
2301// UpsertId is a convenience helper equivalent to:
2302//
2303// info, err := collection.Upsert(bson.M{"_id": id}, update)
2304//
2305// See the Upsert method for more details.
2306func (c *Collection) UpsertId(id interface{}, update interface{}) (info *ChangeInfo, err error) {
2307 return c.Upsert(bson.D{{"_id", id}}, update)
2308}
2309
2310// Remove finds a single document matching the provided selector document
2311// and removes it from the database.
2312// If the session is in safe mode (see SetSafe) a ErrNotFound error is
2313// returned if a document isn't found, or a value of type *LastError
2314// when some other error is detected.
2315//
2316// Relevant documentation:
2317//
2318// http://www.mongodb.org/display/DOCS/Removing
2319//
2320func (c *Collection) Remove(selector interface{}) error {
2321 lerr, err := c.writeQuery(&deleteOp{c.FullName, selector, 1})
2322 if err == nil && lerr != nil && lerr.N == 0 {
2323 return ErrNotFound
2324 }
2325 return err
2326}
2327
2328// RemoveId is a convenience helper equivalent to:
2329//
2330// err := collection.Remove(bson.M{"_id": id})
2331//
2332// See the Remove method for more details.
2333func (c *Collection) RemoveId(id interface{}) error {
2334 return c.Remove(bson.D{{"_id", id}})
2335}
2336
2337// RemoveAll finds all documents matching the provided selector document
2338// and removes them from the database. In case the session is in safe mode
2339// (see the SetSafe method) and an error happens when attempting the change,
2340// the returned error will be of type *LastError.
2341//
2342// Relevant documentation:
2343//
2344// http://www.mongodb.org/display/DOCS/Removing
2345//
2346func (c *Collection) RemoveAll(selector interface{}) (info *ChangeInfo, err error) {
2347 lerr, err := c.writeQuery(&deleteOp{c.FullName, selector, 0})
2348 if err == nil && lerr != nil {
2349 info = &ChangeInfo{Removed: lerr.N}
2350 }
2351 return info, err
2352}
2353
2354// DropDatabase removes the entire database including all of its collections.
2355func (db *Database) DropDatabase() error {
2356 return db.Run(bson.D{{"dropDatabase", 1}}, nil)
2357}
2358
2359// DropCollection removes the entire collection including all of its documents.
2360func (c *Collection) DropCollection() error {
2361 return c.Database.Run(bson.D{{"drop", c.Name}}, nil)
2362}
2363
2364// The CollectionInfo type holds metadata about a collection.
2365//
2366// Relevant documentation:
2367//
2368// http://www.mongodb.org/display/DOCS/createCollection+Command
2369// http://www.mongodb.org/display/DOCS/Capped+Collections
2370//
2371type CollectionInfo struct {
2372 // DisableIdIndex prevents the automatic creation of the index
2373 // on the _id field for the collection.
2374 DisableIdIndex bool
2375
2376 // ForceIdIndex enforces the automatic creation of the index
2377 // on the _id field for the collection. Capped collections,
2378 // for example, do not have such an index by default.
2379 ForceIdIndex bool
2380
2381 // If Capped is true new documents will replace old ones when
2382 // the collection is full. MaxBytes must necessarily be set
2383 // to define the size when the collection wraps around.
2384 // MaxDocs optionally defines the number of documents when it
2385 // wraps, but MaxBytes still needs to be set.
2386 Capped bool
2387 MaxBytes int
2388 MaxDocs int
2389}
2390
2391// Create explicitly creates the c collection with details of info.
2392// MongoDB creates collections automatically on use, so this method
2393// is only necessary when creating collection with non-default
2394// characteristics, such as capped collections.
2395//
2396// Relevant documentation:
2397//
2398// http://www.mongodb.org/display/DOCS/createCollection+Command
2399// http://www.mongodb.org/display/DOCS/Capped+Collections
2400//
2401func (c *Collection) Create(info *CollectionInfo) error {
2402 cmd := make(bson.D, 0, 4)
2403 cmd = append(cmd, bson.DocElem{"create", c.Name})
2404 if info.Capped {
2405 if info.MaxBytes < 1 {
2406 return fmt.Errorf("Collection.Create: with Capped, MaxBytes must also be set")
2407 }
2408 cmd = append(cmd, bson.DocElem{"capped", true})
2409 cmd = append(cmd, bson.DocElem{"size", info.MaxBytes})
2410 if info.MaxDocs > 0 {
2411 cmd = append(cmd, bson.DocElem{"max", info.MaxDocs})
2412 }
2413 }
2414 if info.DisableIdIndex {
2415 cmd = append(cmd, bson.DocElem{"autoIndexId", false})
2416 }
2417 if info.ForceIdIndex {
2418 cmd = append(cmd, bson.DocElem{"autoIndexId", true})
2419 }
2420 return c.Database.Run(cmd, nil)
2421}
2422
2423// Batch sets the batch size used when fetching documents from the database.
2424// It's possible to change this setting on a per-session basis as well, using
2425// the Batch method of Session.
2426//
2427// The default batch size is defined by the database itself. As of this
2428// writing, MongoDB will use an initial size of min(100 docs, 4MB) on the
2429// first batch, and 4MB on remaining ones.
2430func (q *Query) Batch(n int) *Query {
2431 if n == 1 {
2432 // Server interprets 1 as -1 and closes the cursor (!?)
2433 n = 2
2434 }
2435 q.m.Lock()
2436 q.op.limit = int32(n)
2437 q.m.Unlock()
2438 return q
2439}
2440
2441// Prefetch sets the point at which the next batch of results will be requested.
2442// When there are p*batch_size remaining documents cached in an Iter, the next
2443// batch will be requested in background. For instance, when using this:
2444//
2445// query.Batch(200).Prefetch(0.25)
2446//
2447// and there are only 50 documents cached in the Iter to be processed, the
2448// next batch of 200 will be requested. It's possible to change this setting on
2449// a per-session basis as well, using the SetPrefetch method of Session.
2450//
2451// The default prefetch value is 0.25.
2452func (q *Query) Prefetch(p float64) *Query {
2453 q.m.Lock()
2454 q.prefetch = p
2455 q.m.Unlock()
2456 return q
2457}
2458
2459// Skip skips over the n initial documents from the query results. Note that
2460// this only makes sense with capped collections where documents are naturally
2461// ordered by insertion time, or with sorted results.
2462func (q *Query) Skip(n int) *Query {
2463 q.m.Lock()
2464 q.op.skip = int32(n)
2465 q.m.Unlock()
2466 return q
2467}
2468
2469// Limit restricts the maximum number of documents retrieved to n, and also
2470// changes the batch size to the same value. Once n documents have been
2471// returned by Next, the following call will return ErrNotFound.
2472func (q *Query) Limit(n int) *Query {
2473 q.m.Lock()
2474 switch {
2475 case n == 1:
2476 q.limit = 1
2477 q.op.limit = -1
2478 case n == math.MinInt32: // -MinInt32 == -MinInt32
2479 q.limit = math.MaxInt32
2480 q.op.limit = math.MinInt32 + 1
2481 case n < 0:
2482 q.limit = int32(-n)
2483 q.op.limit = int32(n)
2484 default:
2485 q.limit = int32(n)
2486 q.op.limit = int32(n)
2487 }
2488 q.m.Unlock()
2489 return q
2490}
2491
2492// Select enables selecting which fields should be retrieved for the results
2493// found. For example, the following query would only retrieve the name field:
2494//
2495// err := collection.Find(nil).Select(bson.M{"name": 1}).One(&result)
2496//
2497// Relevant documentation:
2498//
2499// http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields
2500//
2501func (q *Query) Select(selector interface{}) *Query {
2502 q.m.Lock()
2503 q.op.selector = selector
2504 q.m.Unlock()
2505 return q
2506}
2507
2508// Sort asks the database to order returned documents according to the
2509// provided field names. A field name may be prefixed by - (minus) for
2510// it to be sorted in reverse order.
2511//
2512// For example:
2513//
2514// query1 := collection.Find(nil).Sort("firstname", "lastname")
2515// query2 := collection.Find(nil).Sort("-age")
2516// query3 := collection.Find(nil).Sort("$natural")
2517// query4 := collection.Find(nil).Select(bson.M{"score": bson.M{"$meta": "textScore"}}).Sort("$textScore:score")
2518//
2519// Relevant documentation:
2520//
2521// http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order
2522//
2523func (q *Query) Sort(fields ...string) *Query {
2524 q.m.Lock()
2525 var order bson.D
2526 for _, field := range fields {
2527 n := 1
2528 var kind string
2529 if field != "" {
2530 if field[0] == '$' {
2531 if c := strings.Index(field, ":"); c > 1 && c < len(field)-1 {
2532 kind = field[1:c]
2533 field = field[c+1:]
2534 }
2535 }
2536 switch field[0] {
2537 case '+':
2538 field = field[1:]
2539 case '-':
2540 n = -1
2541 field = field[1:]
2542 }
2543 }
2544 if field == "" {
2545 panic("Sort: empty field name")
2546 }
2547 if kind == "textScore" {
2548 order = append(order, bson.DocElem{field, bson.M{"$meta": kind}})
2549 } else {
2550 order = append(order, bson.DocElem{field, n})
2551 }
2552 }
2553 q.op.options.OrderBy = order
2554 q.op.hasOptions = true
2555 q.m.Unlock()
2556 return q
2557}
2558
2559// Explain returns a number of details about how the MongoDB server would
2560// execute the requested query, such as the number of objects examined,
2561// the number of times the read lock was yielded to allow writes to go in,
2562// and so on.
2563//
2564// For example:
2565//
2566// m := bson.M{}
2567// err := collection.Find(bson.M{"filename": name}).Explain(m)
2568// if err == nil {
2569// fmt.Printf("Explain: %#v\n", m)
2570// }
2571//
2572// Relevant documentation:
2573//
2574// http://www.mongodb.org/display/DOCS/Optimization
2575// http://www.mongodb.org/display/DOCS/Query+Optimizer
2576//
2577func (q *Query) Explain(result interface{}) error {
2578 q.m.Lock()
2579 clone := &Query{session: q.session, query: q.query}
2580 q.m.Unlock()
2581 clone.op.options.Explain = true
2582 clone.op.hasOptions = true
2583 if clone.op.limit > 0 {
2584 clone.op.limit = -q.op.limit
2585 }
2586 iter := clone.Iter()
2587 if iter.Next(result) {
2588 return nil
2589 }
2590 return iter.Close()
2591}
2592
2593// Hint will include an explicit "hint" in the query to force the server
2594// to use a specified index, potentially improving performance in some
2595// situations. The provided parameters are the fields that compose the
2596// key of the index to be used. For details on how the indexKey may be
2597// built, see the EnsureIndex method.
2598//
2599// For example:
2600//
2601// query := collection.Find(bson.M{"firstname": "Joe", "lastname": "Winter"})
2602// query.Hint("lastname", "firstname")
2603//
2604// Relevant documentation:
2605//
2606// http://www.mongodb.org/display/DOCS/Optimization
2607// http://www.mongodb.org/display/DOCS/Query+Optimizer
2608//
2609func (q *Query) Hint(indexKey ...string) *Query {
2610 q.m.Lock()
2611 keyInfo, err := parseIndexKey(indexKey)
2612 q.op.options.Hint = keyInfo.key
2613 q.op.hasOptions = true
2614 q.m.Unlock()
2615 if err != nil {
2616 panic(err)
2617 }
2618 return q
2619}
2620
2621// SetMaxScan constrains the query to stop after scanning the specified
2622// number of documents.
2623//
2624// This modifier is generally used to prevent potentially long running
2625// queries from disrupting performance by scanning through too much data.
2626func (q *Query) SetMaxScan(n int) *Query {
2627 q.m.Lock()
2628 q.op.options.MaxScan = n
2629 q.op.hasOptions = true
2630 q.m.Unlock()
2631 return q
2632}
2633
2634// SetMaxTime constrains the query to stop after running for the specified time.
2635//
2636// When the time limit is reached MongoDB automatically cancels the query.
2637// This can be used to efficiently prevent and identify unexpectedly slow queries.
2638//
2639// A few important notes about the mechanism enforcing this limit:
2640//
2641// - Requests can block behind locking operations on the server, and that blocking
2642// time is not accounted for. In other words, the timer starts ticking only after
2643// the actual start of the query when it initially acquires the appropriate lock;
2644//
2645// - Operations are interrupted only at interrupt points where an operation can be
2646// safely aborted – the total execution time may exceed the specified value;
2647//
2648// - The limit can be applied to both CRUD operations and commands, but not all
2649// commands are interruptible;
2650//
2651// - While iterating over results, computing follow up batches is included in the
2652// total time and the iteration continues until the alloted time is over, but
2653// network roundtrips are not taken into account for the limit.
2654//
2655// - This limit does not override the inactive cursor timeout for idle cursors
2656// (default is 10 min).
2657//
2658// This mechanism was introduced in MongoDB 2.6.
2659//
2660// Relevant documentation:
2661//
2662// http://blog.mongodb.org/post/83621787773/maxtimems-and-query-optimizer-introspection-in
2663//
2664func (q *Query) SetMaxTime(d time.Duration) *Query {
2665 q.m.Lock()
2666 q.op.options.MaxTimeMS = int(d / time.Millisecond)
2667 q.op.hasOptions = true
2668 q.m.Unlock()
2669 return q
2670}
2671
2672// Snapshot will force the performed query to make use of an available
2673// index on the _id field to prevent the same document from being returned
2674// more than once in a single iteration. This might happen without this
2675// setting in situations when the document changes in size and thus has to
2676// be moved while the iteration is running.
2677//
2678// Because snapshot mode traverses the _id index, it may not be used with
2679// sorting or explicit hints. It also cannot use any other index for the
2680// query.
2681//
2682// Even with snapshot mode, items inserted or deleted during the query may
2683// or may not be returned; that is, this mode is not a true point-in-time
2684// snapshot.
2685//
2686// The same effect of Snapshot may be obtained by using any unique index on
2687// field(s) that will not be modified (best to use Hint explicitly too).
2688// A non-unique index (such as creation time) may be made unique by
2689// appending _id to the index when creating it.
2690//
2691// Relevant documentation:
2692//
2693// http://www.mongodb.org/display/DOCS/How+to+do+Snapshotted+Queries+in+the+Mongo+Database
2694//
2695func (q *Query) Snapshot() *Query {
2696 q.m.Lock()
2697 q.op.options.Snapshot = true
2698 q.op.hasOptions = true
2699 q.m.Unlock()
2700 return q
2701}
2702
2703// Comment adds a comment to the query to identify it in the database profiler output.
2704//
2705// Relevant documentation:
2706//
2707// http://docs.mongodb.org/manual/reference/operator/meta/comment
2708// http://docs.mongodb.org/manual/reference/command/profile
2709// http://docs.mongodb.org/manual/administration/analyzing-mongodb-performance/#database-profiling
2710//
2711func (q *Query) Comment(comment string) *Query {
2712 q.m.Lock()
2713 q.op.options.Comment = comment
2714 q.op.hasOptions = true
2715 q.m.Unlock()
2716 return q
2717}
2718
2719// LogReplay enables an option that optimizes queries that are typically
2720// made on the MongoDB oplog for replaying it. This is an internal
2721// implementation aspect and most likely uninteresting for other uses.
2722// It has seen at least one use case, though, so it's exposed via the API.
2723func (q *Query) LogReplay() *Query {
2724 q.m.Lock()
2725 q.op.flags |= flagLogReplay
2726 q.m.Unlock()
2727 return q
2728}
2729
2730func checkQueryError(fullname string, d []byte) error {
2731 l := len(d)
2732 if l < 16 {
2733 return nil
2734 }
2735 if d[5] == '$' && d[6] == 'e' && d[7] == 'r' && d[8] == 'r' && d[9] == '\x00' && d[4] == '\x02' {
2736 goto Error
2737 }
2738 if len(fullname) < 5 || fullname[len(fullname)-5:] != ".$cmd" {
2739 return nil
2740 }
2741 for i := 0; i+8 < l; i++ {
2742 if d[i] == '\x02' && d[i+1] == 'e' && d[i+2] == 'r' && d[i+3] == 'r' && d[i+4] == 'm' && d[i+5] == 's' && d[i+6] == 'g' && d[i+7] == '\x00' {
2743 goto Error
2744 }
2745 }
2746 return nil
2747
2748Error:
2749 result := &queryError{}
2750 bson.Unmarshal(d, result)
2751 if result.LastError != nil {
2752 return result.LastError
2753 }
2754 if result.Err == "" && result.ErrMsg == "" {
2755 return nil
2756 }
2757 if result.AssertionCode != 0 && result.Assertion != "" {
2758 return &QueryError{Code: result.AssertionCode, Message: result.Assertion, Assertion: true}
2759 }
2760 if result.Err != "" {
2761 return &QueryError{Code: result.Code, Message: result.Err}
2762 }
2763 return &QueryError{Code: result.Code, Message: result.ErrMsg}
2764}
2765
2766// One executes the query and unmarshals the first obtained document into the
2767// result argument. The result must be a struct or map value capable of being
2768// unmarshalled into by gobson. This function blocks until either a result
2769// is available or an error happens. For example:
2770//
2771// err := collection.Find(bson.M{"a", 1}).One(&result)
2772//
2773// In case the resulting document includes a field named $err or errmsg, which
2774// are standard ways for MongoDB to return query errors, the returned err will
2775// be set to a *QueryError value including the Err message and the Code. In
2776// those cases, the result argument is still unmarshalled into with the
2777// received document so that any other custom values may be obtained if
2778// desired.
2779//
2780func (q *Query) One(result interface{}) (err error) {
2781 q.m.Lock()
2782 session := q.session
2783 op := q.op // Copy.
2784 q.m.Unlock()
2785
2786 socket, err := session.acquireSocket(true)
2787 if err != nil {
2788 return err
2789 }
2790 defer socket.Release()
2791
2792 op.flags |= session.slaveOkFlag()
2793 op.limit = -1
2794
2795 data, err := socket.SimpleQuery(&op)
2796 if err != nil {
2797 return err
2798 }
2799 if data == nil {
2800 return ErrNotFound
2801 }
2802 if result != nil {
2803 err = bson.Unmarshal(data, result)
2804 if err == nil {
2805 debugf("Query %p document unmarshaled: %#v", q, result)
2806 } else {
2807 debugf("Query %p document unmarshaling failed: %#v", q, err)
2808 return err
2809 }
2810 }
2811 return checkQueryError(op.collection, data)
2812}
2813
2814// run duplicates the behavior of collection.Find(query).One(&result)
2815// as performed by Database.Run, specializing the logic for running
2816// database commands on a given socket.
2817func (db *Database) run(socket *mongoSocket, cmd, result interface{}) (err error) {
2818 // Database.Run:
2819 if name, ok := cmd.(string); ok {
2820 cmd = bson.D{{name, 1}}
2821 }
2822
2823 // Collection.Find:
2824 session := db.Session
2825 session.m.RLock()
2826 op := session.queryConfig.op // Copy.
2827 session.m.RUnlock()
2828 op.query = cmd
2829 op.collection = db.Name + ".$cmd"
2830
2831 // Query.One:
2832 op.flags |= session.slaveOkFlag()
2833 op.limit = -1
2834
2835 data, err := socket.SimpleQuery(&op)
2836 if err != nil {
2837 return err
2838 }
2839 if data == nil {
2840 return ErrNotFound
2841 }
2842 if result != nil {
2843 err = bson.Unmarshal(data, result)
2844 if err == nil {
2845 var res bson.M
2846 bson.Unmarshal(data, &res)
2847 debugf("Run command unmarshaled: %#v, result: %#v", op, res)
2848 } else {
2849 debugf("Run command unmarshaling failed: %#v", op, err)
2850 return err
2851 }
2852 }
2853 return checkQueryError(op.collection, data)
2854}
2855
2856// The DBRef type implements support for the database reference MongoDB
2857// convention as supported by multiple drivers. This convention enables
2858// cross-referencing documents between collections and databases using
2859// a structure which includes a collection name, a document id, and
2860// optionally a database name.
2861//
2862// See the FindRef methods on Session and on Database.
2863//
2864// Relevant documentation:
2865//
2866// http://www.mongodb.org/display/DOCS/Database+References
2867//
2868type DBRef struct {
2869 Collection string `bson:"$ref"`
2870 Id interface{} `bson:"$id"`
2871 Database string `bson:"$db,omitempty"`
2872}
2873
2874// NOTE: Order of fields for DBRef above does matter, per documentation.
2875
2876// FindRef returns a query that looks for the document in the provided
2877// reference. If the reference includes the DB field, the document will
2878// be retrieved from the respective database.
2879//
2880// See also the DBRef type and the FindRef method on Session.
2881//
2882// Relevant documentation:
2883//
2884// http://www.mongodb.org/display/DOCS/Database+References
2885//
2886func (db *Database) FindRef(ref *DBRef) *Query {
2887 var c *Collection
2888 if ref.Database == "" {
2889 c = db.C(ref.Collection)
2890 } else {
2891 c = db.Session.DB(ref.Database).C(ref.Collection)
2892 }
2893 return c.FindId(ref.Id)
2894}
2895
2896// FindRef returns a query that looks for the document in the provided
2897// reference. For a DBRef to be resolved correctly at the session level
2898// it must necessarily have the optional DB field defined.
2899//
2900// See also the DBRef type and the FindRef method on Database.
2901//
2902// Relevant documentation:
2903//
2904// http://www.mongodb.org/display/DOCS/Database+References
2905//
2906func (s *Session) FindRef(ref *DBRef) *Query {
2907 if ref.Database == "" {
2908 panic(errors.New(fmt.Sprintf("Can't resolve database for %#v", ref)))
2909 }
2910 c := s.DB(ref.Database).C(ref.Collection)
2911 return c.FindId(ref.Id)
2912}
2913
2914// CollectionNames returns the collection names present in the db database.
2915func (db *Database) CollectionNames() (names []string, err error) {
2916 // Clone session and set it to Monotonic mode so that the server
2917 // used for the query may be safely obtained afterwards, if
2918 // necessary for iteration when a cursor is received.
2919 session := db.Session
2920 cloned := session.Clone()
2921 cloned.SetMode(Monotonic, false)
2922 defer cloned.Close()
2923
2924 batchSize := int(cloned.queryConfig.op.limit)
2925
2926 // Try with a command.
2927 var result struct {
2928 Collections []bson.Raw
2929
2930 Cursor struct {
2931 FirstBatch []bson.Raw "firstBatch"
2932 NS string
2933 Id int64
2934 }
2935 }
2936 err = db.With(cloned).Run(bson.D{{"listCollections", 1}, {"cursor", bson.D{{"batchSize", batchSize}}}}, &result)
2937 if err == nil {
2938 firstBatch := result.Collections
2939 if firstBatch == nil {
2940 firstBatch = result.Cursor.FirstBatch
2941 }
2942 var iter *Iter
2943 ns := strings.SplitN(result.Cursor.NS, ".", 2)
2944 if len(ns) < 2 {
2945 iter = db.With(cloned).C("").NewIter(nil, firstBatch, result.Cursor.Id, nil)
2946 } else {
2947 iter = cloned.DB(ns[0]).C(ns[1]).NewIter(nil, firstBatch, result.Cursor.Id, nil)
2948 }
2949 var coll struct{ Name string }
2950 for iter.Next(&coll) {
2951 names = append(names, coll.Name)
2952 }
2953 if err := iter.Close(); err != nil {
2954 return nil, err
2955 }
2956 sort.Strings(names)
2957 return names, err
2958 }
2959 if err != nil && !isNoCmd(err) {
2960 return nil, err
2961 }
2962
2963 // Command not yet supported. Query the database instead.
2964 nameIndex := len(db.Name) + 1
2965 iter := db.C("system.namespaces").Find(nil).Iter()
2966 var coll struct{ Name string }
2967 for iter.Next(&coll) {
2968 if strings.Index(coll.Name, "$") < 0 || strings.Index(coll.Name, ".oplog.$") >= 0 {
2969 names = append(names, coll.Name[nameIndex:])
2970 }
2971 }
2972 if err := iter.Close(); err != nil {
2973 return nil, err
2974 }
2975 sort.Strings(names)
2976 return names, nil
2977}
2978
2979type dbNames struct {
2980 Databases []struct {
2981 Name string
2982 Empty bool
2983 }
2984}
2985
2986// DatabaseNames returns the names of non-empty databases present in the cluster.
2987func (s *Session) DatabaseNames() (names []string, err error) {
2988 var result dbNames
2989 err = s.Run("listDatabases", &result)
2990 if err != nil {
2991 return nil, err
2992 }
2993 for _, db := range result.Databases {
2994 if !db.Empty {
2995 names = append(names, db.Name)
2996 }
2997 }
2998 sort.Strings(names)
2999 return names, nil
3000}
3001
3002// Iter executes the query and returns an iterator capable of going over all
3003// the results. Results will be returned in batches of configurable
3004// size (see the Batch method) and more documents will be requested when a
3005// configurable number of documents is iterated over (see the Prefetch method).
3006func (q *Query) Iter() *Iter {
3007 q.m.Lock()
3008 session := q.session
3009 op := q.op
3010 prefetch := q.prefetch
3011 limit := q.limit
3012 q.m.Unlock()
3013
3014 iter := &Iter{
3015 session: session,
3016 prefetch: prefetch,
3017 limit: limit,
3018 timeout: -1,
3019 }
3020 iter.gotReply.L = &iter.m
3021 iter.op.collection = op.collection
3022 iter.op.limit = op.limit
3023 iter.op.replyFunc = iter.replyFunc()
3024 iter.docsToReceive++
3025 op.replyFunc = iter.op.replyFunc
3026 op.flags |= session.slaveOkFlag()
3027
3028 socket, err := session.acquireSocket(true)
3029 if err != nil {
3030 iter.err = err
3031 } else {
3032 iter.server = socket.Server()
3033 err = socket.Query(&op)
3034 if err != nil {
3035 // Must lock as the query above may call replyFunc.
3036 iter.m.Lock()
3037 iter.err = err
3038 iter.m.Unlock()
3039 }
3040 socket.Release()
3041 }
3042 return iter
3043}
3044
3045// Tail returns a tailable iterator. Unlike a normal iterator, a
3046// tailable iterator may wait for new values to be inserted in the
3047// collection once the end of the current result set is reached,
3048// A tailable iterator may only be used with capped collections.
3049//
3050// The timeout parameter indicates how long Next will block waiting
3051// for a result before timing out. If set to -1, Next will not
3052// timeout, and will continue waiting for a result for as long as
3053// the cursor is valid and the session is not closed. If set to 0,
3054// Next times out as soon as it reaches the end of the result set.
3055// Otherwise, Next will wait for at least the given number of
3056// seconds for a new document to be available before timing out.
3057//
3058// On timeouts, Next will unblock and return false, and the Timeout
3059// method will return true if called. In these cases, Next may still
3060// be called again on the same iterator to check if a new value is
3061// available at the current cursor position, and again it will block
3062// according to the specified timeoutSecs. If the cursor becomes
3063// invalid, though, both Next and Timeout will return false and
3064// the query must be restarted.
3065//
3066// The following example demonstrates timeout handling and query
3067// restarting:
3068//
3069// iter := collection.Find(nil).Sort("$natural").Tail(5 * time.Second)
3070// for {
3071// for iter.Next(&result) {
3072// fmt.Println(result.Id)
3073// lastId = result.Id
3074// }
3075// if iter.Err() != nil {
3076// return iter.Close()
3077// }
3078// if iter.Timeout() {
3079// continue
3080// }
3081// query := collection.Find(bson.M{"_id": bson.M{"$gt": lastId}})
3082// iter = query.Sort("$natural").Tail(5 * time.Second)
3083// }
3084// iter.Close()
3085//
3086// Relevant documentation:
3087//
3088// http://www.mongodb.org/display/DOCS/Tailable+Cursors
3089// http://www.mongodb.org/display/DOCS/Capped+Collections
3090// http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order
3091//
3092func (q *Query) Tail(timeout time.Duration) *Iter {
3093 q.m.Lock()
3094 session := q.session
3095 op := q.op
3096 prefetch := q.prefetch
3097 q.m.Unlock()
3098
3099 iter := &Iter{session: session, prefetch: prefetch}
3100 iter.gotReply.L = &iter.m
3101 iter.timeout = timeout
3102 iter.op.collection = op.collection
3103 iter.op.limit = op.limit
3104 iter.op.replyFunc = iter.replyFunc()
3105 iter.docsToReceive++
3106 op.replyFunc = iter.op.replyFunc
3107 op.flags |= flagTailable | flagAwaitData | session.slaveOkFlag()
3108
3109 socket, err := session.acquireSocket(true)
3110 if err != nil {
3111 iter.err = err
3112 } else {
3113 iter.server = socket.Server()
3114 err = socket.Query(&op)
3115 if err != nil {
3116 // Must lock as the query above may call replyFunc.
3117 iter.m.Lock()
3118 iter.err = err
3119 iter.m.Unlock()
3120 }
3121 socket.Release()
3122 }
3123 return iter
3124}
3125
3126func (s *Session) slaveOkFlag() (flag queryOpFlags) {
3127 s.m.RLock()
3128 if s.slaveOk {
3129 flag = flagSlaveOk
3130 }
3131 s.m.RUnlock()
3132 return
3133}
3134
3135// Err returns nil if no errors happened during iteration, or the actual
3136// error otherwise.
3137//
3138// In case a resulting document included a field named $err or errmsg, which are
3139// standard ways for MongoDB to report an improper query, the returned value has
3140// a *QueryError type, and includes the Err message and the Code.
3141func (iter *Iter) Err() error {
3142 iter.m.Lock()
3143 err := iter.err
3144 iter.m.Unlock()
3145 if err == ErrNotFound {
3146 return nil
3147 }
3148 return err
3149}
3150
3151// Close kills the server cursor used by the iterator, if any, and returns
3152// nil if no errors happened during iteration, or the actual error otherwise.
3153//
3154// Server cursors are automatically closed at the end of an iteration, which
3155// means close will do nothing unless the iteration was interrupted before
3156// the server finished sending results to the driver. If Close is not called
3157// in such a situation, the cursor will remain available at the server until
3158// the default cursor timeout period is reached. No further problems arise.
3159//
3160// Close is idempotent. That means it can be called repeatedly and will
3161// return the same result every time.
3162//
3163// In case a resulting document included a field named $err or errmsg, which are
3164// standard ways for MongoDB to report an improper query, the returned value has
3165// a *QueryError type.
3166func (iter *Iter) Close() error {
3167 iter.m.Lock()
3168 cursorId := iter.op.cursorId
3169 iter.op.cursorId = 0
3170 err := iter.err
3171 iter.m.Unlock()
3172 if cursorId == 0 {
3173 if err == ErrNotFound {
3174 return nil
3175 }
3176 return err
3177 }
3178 socket, err := iter.acquireSocket()
3179 if err == nil {
3180 // TODO Batch kills.
3181 err = socket.Query(&killCursorsOp{[]int64{cursorId}})
3182 socket.Release()
3183 }
3184
3185 iter.m.Lock()
3186 if err != nil && (iter.err == nil || iter.err == ErrNotFound) {
3187 iter.err = err
3188 } else if iter.err != ErrNotFound {
3189 err = iter.err
3190 }
3191 iter.m.Unlock()
3192 return err
3193}
3194
3195// Timeout returns true if Next returned false due to a timeout of
3196// a tailable cursor. In those cases, Next may be called again to continue
3197// the iteration at the previous cursor position.
3198func (iter *Iter) Timeout() bool {
3199 iter.m.Lock()
3200 result := iter.timedout
3201 iter.m.Unlock()
3202 return result
3203}
3204
3205// Next retrieves the next document from the result set, blocking if necessary.
3206// This method will also automatically retrieve another batch of documents from
3207// the server when the current one is exhausted, or before that in background
3208// if pre-fetching is enabled (see the Query.Prefetch and Session.SetPrefetch
3209// methods).
3210//
3211// Next returns true if a document was successfully unmarshalled onto result,
3212// and false at the end of the result set or if an error happened.
3213// When Next returns false, the Err method should be called to verify if
3214// there was an error during iteration.
3215//
3216// For example:
3217//
3218// iter := collection.Find(nil).Iter()
3219// for iter.Next(&result) {
3220// fmt.Printf("Result: %v\n", result.Id)
3221// }
3222// if err := iter.Close(); err != nil {
3223// return err
3224// }
3225//
3226func (iter *Iter) Next(result interface{}) bool {
3227 iter.m.Lock()
3228 iter.timedout = false
3229 timeout := time.Time{}
3230 for iter.err == nil && iter.docData.Len() == 0 && (iter.docsToReceive > 0 || iter.op.cursorId != 0) {
3231 if iter.docsToReceive == 0 {
3232 if iter.timeout >= 0 {
3233 if timeout.IsZero() {
3234 timeout = time.Now().Add(iter.timeout)
3235 }
3236 if time.Now().After(timeout) {
3237 iter.timedout = true
3238 iter.m.Unlock()
3239 return false
3240 }
3241 }
3242 iter.getMore()
3243 if iter.err != nil {
3244 break
3245 }
3246 }
3247 iter.gotReply.Wait()
3248 }
3249
3250 // Exhaust available data before reporting any errors.
3251 if docData, ok := iter.docData.Pop().([]byte); ok {
3252 close := false
3253 if iter.limit > 0 {
3254 iter.limit--
3255 if iter.limit == 0 {
3256 if iter.docData.Len() > 0 {
3257 iter.m.Unlock()
3258 panic(fmt.Errorf("data remains after limit exhausted: %d", iter.docData.Len()))
3259 }
3260 iter.err = ErrNotFound
3261 close = true
3262 }
3263 }
3264 if iter.op.cursorId != 0 && iter.err == nil {
3265 iter.docsBeforeMore--
3266 if iter.docsBeforeMore == -1 {
3267 iter.getMore()
3268 }
3269 }
3270 iter.m.Unlock()
3271
3272 if close {
3273 iter.Close()
3274 }
3275 err := bson.Unmarshal(docData, result)
3276 if err != nil {
3277 debugf("Iter %p document unmarshaling failed: %#v", iter, err)
3278 iter.m.Lock()
3279 if iter.err == nil {
3280 iter.err = err
3281 }
3282 iter.m.Unlock()
3283 return false
3284 }
3285 debugf("Iter %p document unmarshaled: %#v", iter, result)
3286 // XXX Only have to check first document for a query error?
3287 err = checkQueryError(iter.op.collection, docData)
3288 if err != nil {
3289 iter.m.Lock()
3290 if iter.err == nil {
3291 iter.err = err
3292 }
3293 iter.m.Unlock()
3294 return false
3295 }
3296 return true
3297 } else if iter.err != nil {
3298 debugf("Iter %p returning false: %s", iter, iter.err)
3299 iter.m.Unlock()
3300 return false
3301 } else if iter.op.cursorId == 0 {
3302 iter.err = ErrNotFound
3303 debugf("Iter %p exhausted with cursor=0", iter)
3304 iter.m.Unlock()
3305 return false
3306 }
3307
3308 panic("unreachable")
3309}
3310
3311// All retrieves all documents from the result set into the provided slice
3312// and closes the iterator.
3313//
3314// The result argument must necessarily be the address for a slice. The slice
3315// may be nil or previously allocated.
3316//
3317// WARNING: Obviously, All must not be used with result sets that may be
3318// potentially large, since it may consume all memory until the system
3319// crashes. Consider building the query with a Limit clause to ensure the
3320// result size is bounded.
3321//
3322// For instance:
3323//
3324// var result []struct{ Value int }
3325// iter := collection.Find(nil).Limit(100).Iter()
3326// err := iter.All(&result)
3327// if err != nil {
3328// return err
3329// }
3330//
3331func (iter *Iter) All(result interface{}) error {
3332 resultv := reflect.ValueOf(result)
3333 if resultv.Kind() != reflect.Ptr || resultv.Elem().Kind() != reflect.Slice {
3334 panic("result argument must be a slice address")
3335 }
3336 slicev := resultv.Elem()
3337 slicev = slicev.Slice(0, slicev.Cap())
3338 elemt := slicev.Type().Elem()
3339 i := 0
3340 for {
3341 if slicev.Len() == i {
3342 elemp := reflect.New(elemt)
3343 if !iter.Next(elemp.Interface()) {
3344 break
3345 }
3346 slicev = reflect.Append(slicev, elemp.Elem())
3347 slicev = slicev.Slice(0, slicev.Cap())
3348 } else {
3349 if !iter.Next(slicev.Index(i).Addr().Interface()) {
3350 break
3351 }
3352 }
3353 i++
3354 }
3355 resultv.Elem().Set(slicev.Slice(0, i))
3356 return iter.Close()
3357}
3358
3359// All works like Iter.All.
3360func (q *Query) All(result interface{}) error {
3361 return q.Iter().All(result)
3362}
3363
3364// The For method is obsolete and will be removed in a future release.
3365// See Iter as an elegant replacement.
3366func (q *Query) For(result interface{}, f func() error) error {
3367 return q.Iter().For(result, f)
3368}
3369
3370// The For method is obsolete and will be removed in a future release.
3371// See Iter as an elegant replacement.
3372func (iter *Iter) For(result interface{}, f func() error) (err error) {
3373 valid := false
3374 v := reflect.ValueOf(result)
3375 if v.Kind() == reflect.Ptr {
3376 v = v.Elem()
3377 switch v.Kind() {
3378 case reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
3379 valid = v.IsNil()
3380 }
3381 }
3382 if !valid {
3383 panic("For needs a pointer to nil reference value. See the documentation.")
3384 }
3385 zero := reflect.Zero(v.Type())
3386 for {
3387 v.Set(zero)
3388 if !iter.Next(result) {
3389 break
3390 }
3391 err = f()
3392 if err != nil {
3393 return err
3394 }
3395 }
3396 return iter.Err()
3397}
3398
3399// acquireSocket acquires a socket from the same server that the iterator
3400// cursor was obtained from.
3401//
3402// WARNING: This method must not be called with iter.m locked. Acquiring the
3403// socket depends on the cluster sync loop, and the cluster sync loop might
3404// attempt actions which cause replyFunc to be called, inducing a deadlock.
3405func (iter *Iter) acquireSocket() (*mongoSocket, error) {
3406 socket, err := iter.session.acquireSocket(true)
3407 if err != nil {
3408 return nil, err
3409 }
3410 if socket.Server() != iter.server {
3411 // Socket server changed during iteration. This may happen
3412 // with Eventual sessions, if a Refresh is done, or if a
3413 // monotonic session gets a write and shifts from secondary
3414 // to primary. Our cursor is in a specific server, though.
3415 iter.session.m.Lock()
3416 sockTimeout := iter.session.sockTimeout
3417 iter.session.m.Unlock()
3418 socket.Release()
3419 socket, _, err = iter.server.AcquireSocket(0, sockTimeout)
3420 if err != nil {
3421 return nil, err
3422 }
3423 err := iter.session.socketLogin(socket)
3424 if err != nil {
3425 socket.Release()
3426 return nil, err
3427 }
3428 }
3429 return socket, nil
3430}
3431
3432func (iter *Iter) getMore() {
3433 // Increment now so that unlocking the iterator won't cause a
3434 // different goroutine to get here as well.
3435 iter.docsToReceive++
3436 iter.m.Unlock()
3437 socket, err := iter.acquireSocket()
3438 iter.m.Lock()
3439 if err != nil {
3440 iter.err = err
3441 return
3442 }
3443 defer socket.Release()
3444
3445 debugf("Iter %p requesting more documents", iter)
3446 if iter.limit > 0 {
3447 // The -1 below accounts for the fact docsToReceive was incremented above.
3448 limit := iter.limit - int32(iter.docsToReceive-1) - int32(iter.docData.Len())
3449 if limit < iter.op.limit {
3450 iter.op.limit = limit
3451 }
3452 }
3453 if err := socket.Query(&iter.op); err != nil {
3454 iter.docsToReceive--
3455 iter.err = err
3456 }
3457}
3458
3459type countCmd struct {
3460 Count string
3461 Query interface{}
3462 Limit int32 ",omitempty"
3463 Skip int32 ",omitempty"
3464}
3465
3466// Count returns the total number of documents in the result set.
3467func (q *Query) Count() (n int, err error) {
3468 q.m.Lock()
3469 session := q.session
3470 op := q.op
3471 limit := q.limit
3472 q.m.Unlock()
3473
3474 c := strings.Index(op.collection, ".")
3475 if c < 0 {
3476 return 0, errors.New("Bad collection name: " + op.collection)
3477 }
3478
3479 dbname := op.collection[:c]
3480 cname := op.collection[c+1:]
3481 query := op.query
3482 if query == nil {
3483 query = bson.D{}
3484 }
3485 result := struct{ N int }{}
3486 err = session.DB(dbname).Run(countCmd{cname, query, limit, op.skip}, &result)
3487 return result.N, err
3488}
3489
3490// Count returns the total number of documents in the collection.
3491func (c *Collection) Count() (n int, err error) {
3492 return c.Find(nil).Count()
3493}
3494
3495type distinctCmd struct {
3496 Collection string "distinct"
3497 Key string
3498 Query interface{} ",omitempty"
3499}
3500
3501// Distinct unmarshals into result the list of distinct values for the given key.
3502//
3503// For example:
3504//
3505// var result []int
3506// err := collection.Find(bson.M{"gender": "F"}).Distinct("age", &result)
3507//
3508// Relevant documentation:
3509//
3510// http://www.mongodb.org/display/DOCS/Aggregation
3511//
3512func (q *Query) Distinct(key string, result interface{}) error {
3513 q.m.Lock()
3514 session := q.session
3515 op := q.op // Copy.
3516 q.m.Unlock()
3517
3518 c := strings.Index(op.collection, ".")
3519 if c < 0 {
3520 return errors.New("Bad collection name: " + op.collection)
3521 }
3522
3523 dbname := op.collection[:c]
3524 cname := op.collection[c+1:]
3525
3526 var doc struct{ Values bson.Raw }
3527 err := session.DB(dbname).Run(distinctCmd{cname, key, op.query}, &doc)
3528 if err != nil {
3529 return err
3530 }
3531 return doc.Values.Unmarshal(result)
3532}
3533
3534type mapReduceCmd struct {
3535 Collection string "mapreduce"
3536 Map string ",omitempty"
3537 Reduce string ",omitempty"
3538 Finalize string ",omitempty"
3539 Limit int32 ",omitempty"
3540 Out interface{}
3541 Query interface{} ",omitempty"
3542 Sort interface{} ",omitempty"
3543 Scope interface{} ",omitempty"
3544 Verbose bool ",omitempty"
3545}
3546
3547type mapReduceResult struct {
3548 Results bson.Raw
3549 Result bson.Raw
3550 TimeMillis int64 "timeMillis"
3551 Counts struct{ Input, Emit, Output int }
3552 Ok bool
3553 Err string
3554 Timing *MapReduceTime
3555}
3556
3557type MapReduce struct {
3558 Map string // Map Javascript function code (required)
3559 Reduce string // Reduce Javascript function code (required)
3560 Finalize string // Finalize Javascript function code (optional)
3561 Out interface{} // Output collection name or document. If nil, results are inlined into the result parameter.
3562 Scope interface{} // Optional global scope for Javascript functions
3563 Verbose bool
3564}
3565
3566type MapReduceInfo struct {
3567 InputCount int // Number of documents mapped
3568 EmitCount int // Number of times reduce called emit
3569 OutputCount int // Number of documents in resulting collection
3570 Database string // Output database, if results are not inlined
3571 Collection string // Output collection, if results are not inlined
3572 Time int64 // Time to run the job, in nanoseconds
3573 VerboseTime *MapReduceTime // Only defined if Verbose was true
3574}
3575
3576type MapReduceTime struct {
3577 Total int64 // Total time, in nanoseconds
3578 Map int64 "mapTime" // Time within map function, in nanoseconds
3579 EmitLoop int64 "emitLoop" // Time within the emit/map loop, in nanoseconds
3580}
3581
3582// MapReduce executes a map/reduce job for documents covered by the query.
3583// That kind of job is suitable for very flexible bulk aggregation of data
3584// performed at the server side via Javascript functions.
3585//
3586// Results from the job may be returned as a result of the query itself
3587// through the result parameter in case they'll certainly fit in memory
3588// and in a single document. If there's the possibility that the amount
3589// of data might be too large, results must be stored back in an alternative
3590// collection or even a separate database, by setting the Out field of the
3591// provided MapReduce job. In that case, provide nil as the result parameter.
3592//
3593// These are some of the ways to set Out:
3594//
3595// nil
3596// Inline results into the result parameter.
3597//
3598// bson.M{"replace": "mycollection"}
3599// The output will be inserted into a collection which replaces any
3600// existing collection with the same name.
3601//
3602// bson.M{"merge": "mycollection"}
3603// This option will merge new data into the old output collection. In
3604// other words, if the same key exists in both the result set and the
3605// old collection, the new key will overwrite the old one.
3606//
3607// bson.M{"reduce": "mycollection"}
3608// If documents exist for a given key in the result set and in the old
3609// collection, then a reduce operation (using the specified reduce
3610// function) will be performed on the two values and the result will be
3611// written to the output collection. If a finalize function was
3612// provided, this will be run after the reduce as well.
3613//
3614// bson.M{...., "db": "mydb"}
3615// Any of the above options can have the "db" key included for doing
3616// the respective action in a separate database.
3617//
3618// The following is a trivial example which will count the number of
3619// occurrences of a field named n on each document in a collection, and
3620// will return results inline:
3621//
3622// job := &mgo.MapReduce{
3623// Map: "function() { emit(this.n, 1) }",
3624// Reduce: "function(key, values) { return Array.sum(values) }",
3625// }
3626// var result []struct { Id int "_id"; Value int }
3627// _, err := collection.Find(nil).MapReduce(job, &result)
3628// if err != nil {
3629// return err
3630// }
3631// for _, item := range result {
3632// fmt.Println(item.Value)
3633// }
3634//
3635// This function is compatible with MongoDB 1.7.4+.
3636//
3637// Relevant documentation:
3638//
3639// http://www.mongodb.org/display/DOCS/MapReduce
3640//
3641func (q *Query) MapReduce(job *MapReduce, result interface{}) (info *MapReduceInfo, err error) {
3642 q.m.Lock()
3643 session := q.session
3644 op := q.op // Copy.
3645 limit := q.limit
3646 q.m.Unlock()
3647
3648 c := strings.Index(op.collection, ".")
3649 if c < 0 {
3650 return nil, errors.New("Bad collection name: " + op.collection)
3651 }
3652
3653 dbname := op.collection[:c]
3654 cname := op.collection[c+1:]
3655
3656 cmd := mapReduceCmd{
3657 Collection: cname,
3658 Map: job.Map,
3659 Reduce: job.Reduce,
3660 Finalize: job.Finalize,
3661 Out: fixMROut(job.Out),
3662 Scope: job.Scope,
3663 Verbose: job.Verbose,
3664 Query: op.query,
3665 Sort: op.options.OrderBy,
3666 Limit: limit,
3667 }
3668
3669 if cmd.Out == nil {
3670 cmd.Out = bson.D{{"inline", 1}}
3671 }
3672
3673 var doc mapReduceResult
3674 err = session.DB(dbname).Run(&cmd, &doc)
3675 if err != nil {
3676 return nil, err
3677 }
3678 if doc.Err != "" {
3679 return nil, errors.New(doc.Err)
3680 }
3681
3682 info = &MapReduceInfo{
3683 InputCount: doc.Counts.Input,
3684 EmitCount: doc.Counts.Emit,
3685 OutputCount: doc.Counts.Output,
3686 Time: doc.TimeMillis * 1e6,
3687 }
3688
3689 if doc.Result.Kind == 0x02 {
3690 err = doc.Result.Unmarshal(&info.Collection)
3691 info.Database = dbname
3692 } else if doc.Result.Kind == 0x03 {
3693 var v struct{ Collection, Db string }
3694 err = doc.Result.Unmarshal(&v)
3695 info.Collection = v.Collection
3696 info.Database = v.Db
3697 }
3698
3699 if doc.Timing != nil {
3700 info.VerboseTime = doc.Timing
3701 info.VerboseTime.Total *= 1e6
3702 info.VerboseTime.Map *= 1e6
3703 info.VerboseTime.EmitLoop *= 1e6
3704 }
3705
3706 if err != nil {
3707 return nil, err
3708 }
3709 if result != nil {
3710 return info, doc.Results.Unmarshal(result)
3711 }
3712 return info, nil
3713}
3714
3715// The "out" option in the MapReduce command must be ordered. This was
3716// found after the implementation was accepting maps for a long time,
3717// so rather than breaking the API, we'll fix the order if necessary.
3718// Details about the order requirement may be seen in MongoDB's code:
3719//
3720// http://goo.gl/L8jwJX
3721//
3722func fixMROut(out interface{}) interface{} {
3723 outv := reflect.ValueOf(out)
3724 if outv.Kind() != reflect.Map || outv.Type().Key() != reflect.TypeOf("") {
3725 return out
3726 }
3727 outs := make(bson.D, outv.Len())
3728
3729 outTypeIndex := -1
3730 for i, k := range outv.MapKeys() {
3731 ks := k.String()
3732 outs[i].Name = ks
3733 outs[i].Value = outv.MapIndex(k).Interface()
3734 switch ks {
3735 case "normal", "replace", "merge", "reduce", "inline":
3736 outTypeIndex = i
3737 }
3738 }
3739 if outTypeIndex > 0 {
3740 outs[0], outs[outTypeIndex] = outs[outTypeIndex], outs[0]
3741 }
3742 return outs
3743}
3744
3745// Change holds fields for running a findAndModify MongoDB command via
3746// the Query.Apply method.
3747type Change struct {
3748 Update interface{} // The update document
3749 Upsert bool // Whether to insert in case the document isn't found
3750 Remove bool // Whether to remove the document found rather than updating
3751 ReturnNew bool // Should the modified document be returned rather than the old one
3752}
3753
3754type findModifyCmd struct {
3755 Collection string "findAndModify"
3756 Query, Update, Sort, Fields interface{} ",omitempty"
3757 Upsert, Remove, New bool ",omitempty"
3758}
3759
3760type valueResult struct {
3761 Value bson.Raw
3762 LastError LastError "lastErrorObject"
3763}
3764
3765// Apply runs the findAndModify MongoDB command, which allows updating, upserting
3766// or removing a document matching a query and atomically returning either the old
3767// version (the default) or the new version of the document (when ReturnNew is true).
3768// If no objects are found Apply returns ErrNotFound.
3769//
3770// The Sort and Select query methods affect the result of Apply. In case
3771// multiple documents match the query, Sort enables selecting which document to
3772// act upon by ordering it first. Select enables retrieving only a selection
3773// of fields of the new or old document.
3774//
3775// This simple example increments a counter and prints its new value:
3776//
3777// change := mgo.Change{
3778// Update: bson.M{"$inc": bson.M{"n": 1}},
3779// ReturnNew: true,
3780// }
3781// info, err = col.Find(M{"_id": id}).Apply(change, &doc)
3782// fmt.Println(doc.N)
3783//
3784// This method depends on MongoDB >= 2.0 to work properly.
3785//
3786// Relevant documentation:
3787//
3788// http://www.mongodb.org/display/DOCS/findAndModify+Command
3789// http://www.mongodb.org/display/DOCS/Updating
3790// http://www.mongodb.org/display/DOCS/Atomic+Operations
3791//
3792func (q *Query) Apply(change Change, result interface{}) (info *ChangeInfo, err error) {
3793 q.m.Lock()
3794 session := q.session
3795 op := q.op // Copy.
3796 q.m.Unlock()
3797
3798 c := strings.Index(op.collection, ".")
3799 if c < 0 {
3800 return nil, errors.New("bad collection name: " + op.collection)
3801 }
3802
3803 dbname := op.collection[:c]
3804 cname := op.collection[c+1:]
3805
3806 cmd := findModifyCmd{
3807 Collection: cname,
3808 Update: change.Update,
3809 Upsert: change.Upsert,
3810 Remove: change.Remove,
3811 New: change.ReturnNew,
3812 Query: op.query,
3813 Sort: op.options.OrderBy,
3814 Fields: op.selector,
3815 }
3816
3817 session = session.Clone()
3818 defer session.Close()
3819 session.SetMode(Strong, false)
3820
3821 var doc valueResult
3822 err = session.DB(dbname).Run(&cmd, &doc)
3823 if err != nil {
3824 if qerr, ok := err.(*QueryError); ok && qerr.Message == "No matching object found" {
3825 return nil, ErrNotFound
3826 }
3827 return nil, err
3828 }
3829 if doc.LastError.N == 0 {
3830 return nil, ErrNotFound
3831 }
3832 if doc.Value.Kind != 0x0A && result != nil {
3833 err = doc.Value.Unmarshal(result)
3834 if err != nil {
3835 return nil, err
3836 }
3837 }
3838 info = &ChangeInfo{}
3839 lerr := &doc.LastError
3840 if lerr.UpdatedExisting {
3841 info.Updated = lerr.N
3842 } else if change.Remove {
3843 info.Removed = lerr.N
3844 } else if change.Upsert {
3845 info.UpsertedId = lerr.UpsertedId
3846 }
3847 return info, nil
3848}
3849
3850// The BuildInfo type encapsulates details about the running MongoDB server.
3851//
3852// Note that the VersionArray field was introduced in MongoDB 2.0+, but it is
3853// internally assembled from the Version information for previous versions.
3854// In both cases, VersionArray is guaranteed to have at least 4 entries.
3855type BuildInfo struct {
3856 Version string
3857 VersionArray []int `bson:"versionArray"` // On MongoDB 2.0+; assembled from Version otherwise
3858 GitVersion string `bson:"gitVersion"`
3859 OpenSSLVersion string `bson:"OpenSSLVersion"`
3860 SysInfo string `bson:"sysInfo"`
3861 Bits int
3862 Debug bool
3863 MaxObjectSize int `bson:"maxBsonObjectSize"`
3864}
3865
3866// VersionAtLeast returns whether the BuildInfo version is greater than or
3867// equal to the provided version number. If more than one number is
3868// provided, numbers will be considered as major, minor, and so on.
3869func (bi *BuildInfo) VersionAtLeast(version ...int) bool {
3870 for i := range version {
3871 if i == len(bi.VersionArray) {
3872 return false
3873 }
3874 if bi.VersionArray[i] < version[i] {
3875 return false
3876 }
3877 }
3878 return true
3879}
3880
3881// BuildInfo retrieves the version and other details about the
3882// running MongoDB server.
3883func (s *Session) BuildInfo() (info BuildInfo, err error) {
3884 err = s.Run(bson.D{{"buildInfo", "1"}}, &info)
3885 if len(info.VersionArray) == 0 {
3886 for _, a := range strings.Split(info.Version, ".") {
3887 i, err := strconv.Atoi(a)
3888 if err != nil {
3889 break
3890 }
3891 info.VersionArray = append(info.VersionArray, i)
3892 }
3893 }
3894 for len(info.VersionArray) < 4 {
3895 info.VersionArray = append(info.VersionArray, 0)
3896 }
3897 if i := strings.IndexByte(info.GitVersion, ' '); i >= 0 {
3898 // Strip off the " modules: enterprise" suffix. This is a _git version_.
3899 // That information may be moved to another field if people need it.
3900 info.GitVersion = info.GitVersion[:i]
3901 }
3902 return
3903}
3904
3905// ---------------------------------------------------------------------------
3906// Internal session handling helpers.
3907
3908func (s *Session) acquireSocket(slaveOk bool) (*mongoSocket, error) {
3909
3910 // Read-only lock to check for previously reserved socket.
3911 s.m.RLock()
3912 if s.masterSocket != nil {
3913 socket := s.masterSocket
3914 socket.Acquire()
3915 s.m.RUnlock()
3916 return socket, nil
3917 }
3918 if s.slaveSocket != nil && s.slaveOk && slaveOk {
3919 socket := s.slaveSocket
3920 socket.Acquire()
3921 s.m.RUnlock()
3922 return socket, nil
3923 }
3924 s.m.RUnlock()
3925
3926 // No go. We may have to request a new socket and change the session,
3927 // so try again but with an exclusive lock now.
3928 s.m.Lock()
3929 defer s.m.Unlock()
3930
3931 if s.masterSocket != nil {
3932 s.masterSocket.Acquire()
3933 return s.masterSocket, nil
3934 }
3935 if s.slaveSocket != nil && s.slaveOk && slaveOk {
3936 s.slaveSocket.Acquire()
3937 return s.slaveSocket, nil
3938 }
3939
3940 // Still not good. We need a new socket.
3941 sock, err := s.cluster().AcquireSocket(slaveOk && s.slaveOk, s.syncTimeout, s.sockTimeout, s.queryConfig.op.serverTags, s.poolLimit)
3942 if err != nil {
3943 return nil, err
3944 }
3945
3946 // Authenticate the new socket.
3947 if err = s.socketLogin(sock); err != nil {
3948 sock.Release()
3949 return nil, err
3950 }
3951
3952 // Keep track of the new socket, if necessary.
3953 // Note that, as a special case, if the Eventual session was
3954 // not refreshed (s.slaveSocket != nil), it means the developer
3955 // asked to preserve an existing reserved socket, so we'll
3956 // keep a master one around too before a Refresh happens.
3957 if s.consistency != Eventual || s.slaveSocket != nil {
3958 s.setSocket(sock)
3959 }
3960
3961 // Switch over a Monotonic session to the master.
3962 if !slaveOk && s.consistency == Monotonic {
3963 s.slaveOk = false
3964 }
3965
3966 return sock, nil
3967}
3968
3969// setSocket binds socket to this section.
3970func (s *Session) setSocket(socket *mongoSocket) {
3971 info := socket.Acquire()
3972 if info.Master {
3973 if s.masterSocket != nil {
3974 panic("setSocket(master) with existing master socket reserved")
3975 }
3976 s.masterSocket = socket
3977 } else {
3978 if s.slaveSocket != nil {
3979 panic("setSocket(slave) with existing slave socket reserved")
3980 }
3981 s.slaveSocket = socket
3982 }
3983}
3984
3985// unsetSocket releases any slave and/or master sockets reserved.
3986func (s *Session) unsetSocket() {
3987 if s.masterSocket != nil {
3988 s.masterSocket.Release()
3989 }
3990 if s.slaveSocket != nil {
3991 s.slaveSocket.Release()
3992 }
3993 s.masterSocket = nil
3994 s.slaveSocket = nil
3995}
3996
3997func (iter *Iter) replyFunc() replyFunc {
3998 return func(err error, op *replyOp, docNum int, docData []byte) {
3999 iter.m.Lock()
4000 iter.docsToReceive--
4001 if err != nil {
4002 iter.err = err
4003 debugf("Iter %p received an error: %s", iter, err.Error())
4004 } else if docNum == -1 {
4005 debugf("Iter %p received no documents (cursor=%d).", iter, op.cursorId)
4006 if op != nil && op.cursorId != 0 {
4007 // It's a tailable cursor.
4008 iter.op.cursorId = op.cursorId
4009 } else if op != nil && op.cursorId == 0 && op.flags&1 == 1 {
4010 // Cursor likely timed out.
4011 iter.err = ErrCursor
4012 } else {
4013 iter.err = ErrNotFound
4014 }
4015 } else {
4016 rdocs := int(op.replyDocs)
4017 if docNum == 0 {
4018 iter.docsToReceive += rdocs - 1
4019 docsToProcess := iter.docData.Len() + rdocs
4020 if iter.limit == 0 || int32(docsToProcess) < iter.limit {
4021 iter.docsBeforeMore = docsToProcess - int(iter.prefetch*float64(rdocs))
4022 } else {
4023 iter.docsBeforeMore = -1
4024 }
4025 iter.op.cursorId = op.cursorId
4026 }
4027 // XXX Handle errors and flags.
4028 debugf("Iter %p received reply document %d/%d (cursor=%d)", iter, docNum+1, rdocs, op.cursorId)
4029 iter.docData.Push(docData)
4030 }
4031 iter.gotReply.Broadcast()
4032 iter.m.Unlock()
4033 }
4034}
4035
4036type writeCmdResult struct {
4037 Ok bool
4038 N int
4039 NModified int `bson:"nModified"`
4040 Upserted []struct {
4041 Index int
4042 Id interface{} `_id`
4043 }
4044 Errors []struct {
4045 Ok bool
4046 Index int
4047 Code int
4048 N int
4049 ErrMsg string
4050 } `bson:"writeErrors"`
4051 ConcernError struct {
4052 Code int
4053 ErrMsg string
4054 } `bson:"writeConcernError"`
4055}
4056
4057// writeQuery runs the given modifying operation, potentially followed up
4058// by a getLastError command in case the session is in safe mode. The
4059// LastError result is made available in lerr, and if lerr.Err is set it
4060// will also be returned as err.
4061func (c *Collection) writeQuery(op interface{}) (lerr *LastError, err error) {
4062 s := c.Database.Session
4063 dbname := c.Database.Name
4064 socket, err := s.acquireSocket(dbname == "local")
4065 if err != nil {
4066 return nil, err
4067 }
4068 defer socket.Release()
4069
4070 s.m.RLock()
4071 safeOp := s.safeOp
4072 s.m.RUnlock()
4073
4074 // TODO Enable this path for wire version 2 as well.
4075 if socket.ServerInfo().MaxWireVersion >= 3 {
4076 // Servers with a more recent write protocol benefit from write commands.
4077 if op, ok := op.(*insertOp); ok && len(op.documents) > 1000 {
4078 var firstErr error
4079 // Maximum batch size is 1000. Must split out in separate operations for compatibility.
4080 all := op.documents
4081 for i := 0; i < len(all); i += 1000 {
4082 l := i + 1000
4083 if l > len(all) {
4084 l = len(all)
4085 }
4086 op.documents = all[i:l]
4087 _, err := c.writeCommand(socket, safeOp, op)
4088 if err != nil {
4089 if op.flags&1 != 0 {
4090 if firstErr == nil {
4091 firstErr = err
4092 }
4093 } else {
4094 return nil, err
4095 }
4096 }
4097 }
4098 return nil, firstErr
4099 }
4100 return c.writeCommand(socket, safeOp, op)
4101 }
4102
4103 if safeOp == nil {
4104 return nil, socket.Query(op)
4105 }
4106
4107 var mutex sync.Mutex
4108 var replyData []byte
4109 var replyErr error
4110 mutex.Lock()
4111 query := *safeOp // Copy the data.
4112 query.collection = dbname + ".$cmd"
4113 query.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
4114 replyData = docData
4115 replyErr = err
4116 mutex.Unlock()
4117 }
4118 err = socket.Query(op, &query)
4119 if err != nil {
4120 return nil, err
4121 }
4122 mutex.Lock() // Wait.
4123 if replyErr != nil {
4124 return nil, replyErr // XXX TESTME
4125 }
4126 if hasErrMsg(replyData) {
4127 // Looks like getLastError itself failed.
4128 err = checkQueryError(query.collection, replyData)
4129 if err != nil {
4130 return nil, err
4131 }
4132 }
4133 result := &LastError{}
4134 bson.Unmarshal(replyData, &result)
4135 debugf("Result from writing query: %#v", result)
4136 if result.Err != "" {
4137 return result, result
4138 }
4139 return result, nil
4140}
4141
4142func (c *Collection) writeCommand(socket *mongoSocket, safeOp *queryOp, op interface{}) (lerr *LastError, err error) {
4143 var writeConcern interface{}
4144 if safeOp == nil {
4145 writeConcern = bson.D{{"w", 0}}
4146 } else {
4147 writeConcern = safeOp.query.(*getLastError)
4148 }
4149
4150 var cmd bson.D
4151 switch op := op.(type) {
4152 case *insertOp:
4153 // http://docs.mongodb.org/manual/reference/command/insert
4154 cmd = bson.D{
4155 {"insert", c.Name},
4156 {"documents", op.documents},
4157 {"writeConcern", writeConcern},
4158 {"ordered", op.flags&1 == 0},
4159 }
4160 case *updateOp:
4161 // http://docs.mongodb.org/manual/reference/command/update
4162 selector := op.selector
4163 if selector == nil {
4164 selector = bson.D{}
4165 }
4166 cmd = bson.D{
4167 {"update", c.Name},
4168 {"updates", []bson.D{{{"q", selector}, {"u", op.update}, {"upsert", op.flags&1 != 0}, {"multi", op.flags&2 != 0}}}},
4169 {"writeConcern", writeConcern},
4170 //{"ordered", <bool>},
4171 }
4172 case *deleteOp:
4173 // http://docs.mongodb.org/manual/reference/command/delete
4174 selector := op.selector
4175 if selector == nil {
4176 selector = bson.D{}
4177 }
4178 cmd = bson.D{
4179 {"delete", c.Name},
4180 {"deletes", []bson.D{{{"q", selector}, {"limit", op.flags & 1}}}},
4181 {"writeConcern", writeConcern},
4182 //{"ordered", <bool>},
4183 }
4184 }
4185
4186 var result writeCmdResult
4187 err = c.Database.run(socket, cmd, &result)
4188 debugf("Write command result: %#v (err=%v)", result, err)
4189 lerr = &LastError{
4190 UpdatedExisting: result.N > 0 && len(result.Upserted) == 0,
4191 N: result.N,
4192 }
4193 if len(result.Upserted) > 0 {
4194 lerr.UpsertedId = result.Upserted[0].Id
4195 }
4196 if len(result.Errors) > 0 {
4197 e := result.Errors[0]
4198 if !e.Ok {
4199 lerr.Code = e.Code
4200 lerr.Err = e.ErrMsg
4201 err = lerr
4202 }
4203 } else if result.ConcernError.Code != 0 {
4204 e := result.ConcernError
4205 lerr.Code = e.Code
4206 lerr.Err = e.ErrMsg
4207 err = lerr
4208 }
4209
4210 if err == nil && safeOp == nil {
4211 return nil, nil
4212 }
4213 return lerr, err
4214}
4215
4216func hasErrMsg(d []byte) bool {
4217 l := len(d)
4218 for i := 0; i+8 < l; i++ {
4219 if d[i] == '\x02' && d[i+1] == 'e' && d[i+2] == 'r' && d[i+3] == 'r' && d[i+4] == 'm' && d[i+5] == 's' && d[i+6] == 'g' && d[i+7] == '\x00' {
4220 return true
4221 }
4222 }
4223 return false
4224}
vendor/gopkg.in/mgo.v2/session_test.go+3704−0
@@ -0,0 +1,3704 @@
1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11// list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13// this list of conditions and the following disclaimer in the documentation
14// and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo_test
28
29import (
30 "flag"
31 "fmt"
32 "math"
33 "reflect"
34 "runtime"
35 "sort"
36 "strconv"
37 "strings"
38 "time"
39
40 . "gopkg.in/check.v1"
41 "gopkg.in/mgo.v2"
42 "gopkg.in/mgo.v2/bson"
43)
44
45func (s *S) TestRunString(c *C) {
46 session, err := mgo.Dial("localhost:40001")
47 c.Assert(err, IsNil)
48 defer session.Close()
49
50 result := struct{ Ok int }{}
51 err = session.Run("ping", &result)
52 c.Assert(err, IsNil)
53 c.Assert(result.Ok, Equals, 1)
54}
55
56func (s *S) TestRunValue(c *C) {
57 session, err := mgo.Dial("localhost:40001")
58 c.Assert(err, IsNil)
59 defer session.Close()
60
61 result := struct{ Ok int }{}
62 err = session.Run(M{"ping": 1}, &result)
63 c.Assert(err, IsNil)
64 c.Assert(result.Ok, Equals, 1)
65}
66
67func (s *S) TestPing(c *C) {
68 session, err := mgo.Dial("localhost:40001")
69 c.Assert(err, IsNil)
70 defer session.Close()
71
72 // Just ensure the nonce has been received.
73 result := struct{}{}
74 err = session.Run("ping", &result)
75
76 mgo.ResetStats()
77
78 err = session.Ping()
79 c.Assert(err, IsNil)
80
81 // Pretty boring.
82 stats := mgo.GetStats()
83 c.Assert(stats.SentOps, Equals, 1)
84 c.Assert(stats.ReceivedOps, Equals, 1)
85}
86
87func (s *S) TestURLSingle(c *C) {
88 session, err := mgo.Dial("mongodb://localhost:40001/")
89 c.Assert(err, IsNil)
90 defer session.Close()
91
92 result := struct{ Ok int }{}
93 err = session.Run("ping", &result)
94 c.Assert(err, IsNil)
95 c.Assert(result.Ok, Equals, 1)
96}
97
98func (s *S) TestURLMany(c *C) {
99 session, err := mgo.Dial("mongodb://localhost:40011,localhost:40012/")
100 c.Assert(err, IsNil)
101 defer session.Close()
102
103 result := struct{ Ok int }{}
104 err = session.Run("ping", &result)
105 c.Assert(err, IsNil)
106 c.Assert(result.Ok, Equals, 1)
107}
108
109func (s *S) TestURLParsing(c *C) {
110 urls := []string{
111 "localhost:40001?foo=1&bar=2",
112 "localhost:40001?foo=1;bar=2",
113 }
114 for _, url := range urls {
115 session, err := mgo.Dial(url)
116 if session != nil {
117 session.Close()
118 }
119 c.Assert(err, ErrorMatches, "unsupported connection URL option: (foo=1|bar=2)")
120 }
121}
122
123func (s *S) TestInsertFindOne(c *C) {
124 session, err := mgo.Dial("localhost:40001")
125 c.Assert(err, IsNil)
126 defer session.Close()
127
128 coll := session.DB("mydb").C("mycoll")
129 err = coll.Insert(M{"a": 1, "b": 2})
130 c.Assert(err, IsNil)
131 err = coll.Insert(M{"a": 1, "b": 3})
132 c.Assert(err, IsNil)
133
134 result := struct{ A, B int }{}
135
136 err = coll.Find(M{"a": 1}).Sort("b").One(&result)
137 c.Assert(err, IsNil)
138 c.Assert(result.A, Equals, 1)
139 c.Assert(result.B, Equals, 2)
140}
141
142func (s *S) TestInsertFindOneNil(c *C) {
143 session, err := mgo.Dial("localhost:40002")
144 c.Assert(err, IsNil)
145 defer session.Close()
146
147 coll := session.DB("mydb").C("mycoll")
148 err = coll.Find(nil).One(nil)
149 c.Assert(err, ErrorMatches, "unauthorized.*|not authorized.*")
150}
151
152func (s *S) TestInsertFindOneMap(c *C) {
153 session, err := mgo.Dial("localhost:40001")
154 c.Assert(err, IsNil)
155 defer session.Close()
156
157 coll := session.DB("mydb").C("mycoll")
158 err = coll.Insert(M{"a": 1, "b": 2})
159 c.Assert(err, IsNil)
160 result := make(M)
161 err = coll.Find(M{"a": 1}).One(result)
162 c.Assert(err, IsNil)
163 c.Assert(result["a"], Equals, 1)
164 c.Assert(result["b"], Equals, 2)
165}
166
167func (s *S) TestInsertFindAll(c *C) {
168 session, err := mgo.Dial("localhost:40001")
169 c.Assert(err, IsNil)
170 defer session.Close()
171
172 coll := session.DB("mydb").C("mycoll")
173 err = coll.Insert(M{"a": 1, "b": 2})
174 c.Assert(err, IsNil)
175 err = coll.Insert(M{"a": 3, "b": 4})
176 c.Assert(err, IsNil)
177
178 type R struct{ A, B int }
179 var result []R
180
181 assertResult := func() {
182 c.Assert(len(result), Equals, 2)
183 c.Assert(result[0].A, Equals, 1)
184 c.Assert(result[0].B, Equals, 2)
185 c.Assert(result[1].A, Equals, 3)
186 c.Assert(result[1].B, Equals, 4)
187 }
188
189 // nil slice
190 err = coll.Find(nil).Sort("a").All(&result)
191 c.Assert(err, IsNil)
192 assertResult()
193
194 // Previously allocated slice
195 allocd := make([]R, 5)
196 result = allocd
197 err = coll.Find(nil).Sort("a").All(&result)
198 c.Assert(err, IsNil)
199 assertResult()
200
201 // Ensure result is backed by the originally allocated array
202 c.Assert(&result[0], Equals, &allocd[0])
203
204 // Non-pointer slice error
205 f := func() { coll.Find(nil).All(result) }
206 c.Assert(f, Panics, "result argument must be a slice address")
207
208 // Non-slice error
209 f = func() { coll.Find(nil).All(new(int)) }
210 c.Assert(f, Panics, "result argument must be a slice address")
211}
212
213func (s *S) TestFindRef(c *C) {
214 session, err := mgo.Dial("localhost:40001")
215 c.Assert(err, IsNil)
216 defer session.Close()
217
218 db1 := session.DB("db1")
219 db1col1 := db1.C("col1")
220
221 db2 := session.DB("db2")
222 db2col1 := db2.C("col1")
223
224 err = db1col1.Insert(M{"_id": 1, "n": 1})
225 c.Assert(err, IsNil)
226 err = db1col1.Insert(M{"_id": 2, "n": 2})
227 c.Assert(err, IsNil)
228 err = db2col1.Insert(M{"_id": 2, "n": 3})
229 c.Assert(err, IsNil)
230
231 result := struct{ N int }{}
232
233 ref1 := &mgo.DBRef{Collection: "col1", Id: 1}
234 ref2 := &mgo.DBRef{Collection: "col1", Id: 2, Database: "db2"}
235
236 err = db1.FindRef(ref1).One(&result)
237 c.Assert(err, IsNil)
238 c.Assert(result.N, Equals, 1)
239
240 err = db1.FindRef(ref2).One(&result)
241 c.Assert(err, IsNil)
242 c.Assert(result.N, Equals, 3)
243
244 err = db2.FindRef(ref1).One(&result)
245 c.Assert(err, Equals, mgo.ErrNotFound)
246
247 err = db2.FindRef(ref2).One(&result)
248 c.Assert(err, IsNil)
249 c.Assert(result.N, Equals, 3)
250
251 err = session.FindRef(ref2).One(&result)
252 c.Assert(err, IsNil)
253 c.Assert(result.N, Equals, 3)
254
255 f := func() { session.FindRef(ref1).One(&result) }
256 c.Assert(f, PanicMatches, "Can't resolve database for &mgo.DBRef{Collection:\"col1\", Id:1, Database:\"\"}")
257}
258
259func (s *S) TestDatabaseAndCollectionNames(c *C) {
260 session, err := mgo.Dial("localhost:40001")
261 c.Assert(err, IsNil)
262 defer session.Close()
263
264 db1 := session.DB("db1")
265 db1col1 := db1.C("col1")
266 db1col2 := db1.C("col2")
267
268 db2 := session.DB("db2")
269 db2col1 := db2.C("col3")
270
271 err = db1col1.Insert(M{"_id": 1})
272 c.Assert(err, IsNil)
273 err = db1col2.Insert(M{"_id": 1})
274 c.Assert(err, IsNil)
275 err = db2col1.Insert(M{"_id": 1})
276 c.Assert(err, IsNil)
277
278 names, err := session.DatabaseNames()
279 c.Assert(err, IsNil)
280 if !reflect.DeepEqual(names, []string{"db1", "db2"}) {
281 // 2.4+ has "local" as well.
282 c.Assert(names, DeepEquals, []string{"db1", "db2", "local"})
283 }
284
285 // Try to exercise cursor logic. 2.8.0-rc3 still ignores this.
286 session.SetBatch(2)
287
288 names, err = db1.CollectionNames()
289 c.Assert(err, IsNil)
290 c.Assert(names, DeepEquals, []string{"col1", "col2", "system.indexes"})
291
292 names, err = db2.CollectionNames()
293 c.Assert(err, IsNil)
294 c.Assert(names, DeepEquals, []string{"col3", "system.indexes"})
295}
296
297func (s *S) TestSelect(c *C) {
298 session, err := mgo.Dial("localhost:40001")
299 c.Assert(err, IsNil)
300 defer session.Close()
301
302 coll := session.DB("mydb").C("mycoll")
303 coll.Insert(M{"a": 1, "b": 2})
304
305 result := struct{ A, B int }{}
306
307 err = coll.Find(M{"a": 1}).Select(M{"b": 1}).One(&result)
308 c.Assert(err, IsNil)
309 c.Assert(result.A, Equals, 0)
310 c.Assert(result.B, Equals, 2)
311}
312
313func (s *S) TestInlineMap(c *C) {
314 session, err := mgo.Dial("localhost:40001")
315 c.Assert(err, IsNil)
316 defer session.Close()
317
318 coll := session.DB("mydb").C("mycoll")
319
320 var v, result1 struct {
321 A int
322 M map[string]int ",inline"
323 }
324
325 v.A = 1
326 v.M = map[string]int{"b": 2}
327 err = coll.Insert(v)
328 c.Assert(err, IsNil)
329
330 noId := M{"_id": 0}
331
332 err = coll.Find(nil).Select(noId).One(&result1)
333 c.Assert(err, IsNil)
334 c.Assert(result1.A, Equals, 1)
335 c.Assert(result1.M, DeepEquals, map[string]int{"b": 2})
336
337 var result2 M
338 err = coll.Find(nil).Select(noId).One(&result2)
339 c.Assert(err, IsNil)
340 c.Assert(result2, DeepEquals, M{"a": 1, "b": 2})
341
342}
343
344func (s *S) TestUpdate(c *C) {
345 session, err := mgo.Dial("localhost:40001")
346 c.Assert(err, IsNil)
347 defer session.Close()
348
349 coll := session.DB("mydb").C("mycoll")
350
351 ns := []int{40, 41, 42, 43, 44, 45, 46}
352 for _, n := range ns {
353 err := coll.Insert(M{"k": n, "n": n})
354 c.Assert(err, IsNil)
355 }
356
357 // No changes is a no-op and shouldn't return an error.
358 err = coll.Update(M{"k": 42}, M{"$set": M{"n": 42}})
359 c.Assert(err, IsNil)
360
361 err = coll.Update(M{"k": 42}, M{"$inc": M{"n": 1}})
362 c.Assert(err, IsNil)
363
364 result := make(M)
365 err = coll.Find(M{"k": 42}).One(result)
366 c.Assert(err, IsNil)
367 c.Assert(result["n"], Equals, 43)
368
369 err = coll.Update(M{"k": 47}, M{"k": 47, "n": 47})
370 c.Assert(err, Equals, mgo.ErrNotFound)
371
372 err = coll.Find(M{"k": 47}).One(result)
373 c.Assert(err, Equals, mgo.ErrNotFound)
374}
375
376func (s *S) TestUpdateId(c *C) {
377 session, err := mgo.Dial("localhost:40001")
378 c.Assert(err, IsNil)
379 defer session.Close()
380
381 coll := session.DB("mydb").C("mycoll")
382
383 ns := []int{40, 41, 42, 43, 44, 45, 46}
384 for _, n := range ns {
385 err := coll.Insert(M{"_id": n, "n": n})
386 c.Assert(err, IsNil)
387 }
388
389 err = coll.UpdateId(42, M{"$inc": M{"n": 1}})
390 c.Assert(err, IsNil)
391
392 result := make(M)
393 err = coll.FindId(42).One(result)
394 c.Assert(err, IsNil)
395 c.Assert(result["n"], Equals, 43)
396
397 err = coll.UpdateId(47, M{"k": 47, "n": 47})
398 c.Assert(err, Equals, mgo.ErrNotFound)
399
400 err = coll.FindId(47).One(result)
401 c.Assert(err, Equals, mgo.ErrNotFound)
402}
403
404func (s *S) TestUpdateNil(c *C) {
405 session, err := mgo.Dial("localhost:40001")
406 c.Assert(err, IsNil)
407 defer session.Close()
408
409 coll := session.DB("mydb").C("mycoll")
410
411 err = coll.Insert(M{"k": 42, "n": 42})
412 c.Assert(err, IsNil)
413 err = coll.Update(nil, M{"$inc": M{"n": 1}})
414 c.Assert(err, IsNil)
415
416 result := make(M)
417 err = coll.Find(M{"k": 42}).One(result)
418 c.Assert(err, IsNil)
419 c.Assert(result["n"], Equals, 43)
420
421 err = coll.Insert(M{"k": 45, "n": 45})
422 c.Assert(err, IsNil)
423 _, err = coll.UpdateAll(nil, M{"$inc": M{"n": 1}})
424 c.Assert(err, IsNil)
425
426 err = coll.Find(M{"k": 42}).One(result)
427 c.Assert(err, IsNil)
428 c.Assert(result["n"], Equals, 44)
429 err = coll.Find(M{"k": 45}).One(result)
430 c.Assert(err, IsNil)
431 c.Assert(result["n"], Equals, 46)
432}
433
434func (s *S) TestUpsert(c *C) {
435 session, err := mgo.Dial("localhost:40001")
436 c.Assert(err, IsNil)
437 defer session.Close()
438
439 coll := session.DB("mydb").C("mycoll")
440
441 ns := []int{40, 41, 42, 43, 44, 45, 46}
442 for _, n := range ns {
443 err := coll.Insert(M{"k": n, "n": n})
444 c.Assert(err, IsNil)
445 }
446
447 info, err := coll.Upsert(M{"k": 42}, M{"k": 42, "n": 24})
448 c.Assert(err, IsNil)
449 c.Assert(info.Updated, Equals, 1)
450 c.Assert(info.UpsertedId, IsNil)
451
452 result := M{}
453 err = coll.Find(M{"k": 42}).One(result)
454 c.Assert(err, IsNil)
455 c.Assert(result["n"], Equals, 24)
456
457 // Insert with internally created id.
458 info, err = coll.Upsert(M{"k": 47}, M{"k": 47, "n": 47})
459 c.Assert(err, IsNil)
460 c.Assert(info.Updated, Equals, 0)
461 c.Assert(info.UpsertedId, NotNil)
462
463 err = coll.Find(M{"k": 47}).One(result)
464 c.Assert(err, IsNil)
465 c.Assert(result["n"], Equals, 47)
466
467 result = M{}
468 err = coll.Find(M{"_id": info.UpsertedId}).One(result)
469 c.Assert(err, IsNil)
470 c.Assert(result["n"], Equals, 47)
471
472 // Insert with provided id.
473 info, err = coll.Upsert(M{"k": 48}, M{"k": 48, "n": 48, "_id": 48})
474 c.Assert(err, IsNil)
475 c.Assert(info.Updated, Equals, 0)
476 if s.versionAtLeast(2, 6) {
477 c.Assert(info.UpsertedId, Equals, 48)
478 } else {
479 c.Assert(info.UpsertedId, IsNil) // Unfortunate, but that's what Mongo gave us.
480 }
481
482 err = coll.Find(M{"k": 48}).One(result)
483 c.Assert(err, IsNil)
484 c.Assert(result["n"], Equals, 48)
485}
486
487func (s *S) TestUpsertId(c *C) {
488 session, err := mgo.Dial("localhost:40001")
489 c.Assert(err, IsNil)
490 defer session.Close()
491
492 coll := session.DB("mydb").C("mycoll")
493
494 ns := []int{40, 41, 42, 43, 44, 45, 46}
495 for _, n := range ns {
496 err := coll.Insert(M{"_id": n, "n": n})
497 c.Assert(err, IsNil)
498 }
499
500 info, err := coll.UpsertId(42, M{"n": 24})
501 c.Assert(err, IsNil)
502 c.Assert(info.Updated, Equals, 1)
503 c.Assert(info.UpsertedId, IsNil)
504
505 result := M{}
506 err = coll.FindId(42).One(result)
507 c.Assert(err, IsNil)
508 c.Assert(result["n"], Equals, 24)
509
510 info, err = coll.UpsertId(47, M{"_id": 47, "n": 47})
511 c.Assert(err, IsNil)
512 c.Assert(info.Updated, Equals, 0)
513 if s.versionAtLeast(2, 6) {
514 c.Assert(info.UpsertedId, Equals, 47)
515 } else {
516 c.Assert(info.UpsertedId, IsNil)
517 }
518
519 err = coll.FindId(47).One(result)
520 c.Assert(err, IsNil)
521 c.Assert(result["n"], Equals, 47)
522}
523
524func (s *S) TestUpdateAll(c *C) {
525 session, err := mgo.Dial("localhost:40001")
526 c.Assert(err, IsNil)
527 defer session.Close()
528
529 coll := session.DB("mydb").C("mycoll")
530
531 ns := []int{40, 41, 42, 43, 44, 45, 46}
532 for _, n := range ns {
533 err := coll.Insert(M{"k": n, "n": n})
534 c.Assert(err, IsNil)
535 }
536
537 // Don't actually modify the documents. Should still report 4 matching updates.
538 info, err := coll.UpdateAll(M{"k": M{"$gt": 42}}, M{"$unset": M{"missing": 1}})
539 c.Assert(err, IsNil)
540 c.Assert(info.Updated, Equals, 4)
541
542 info, err = coll.UpdateAll(M{"k": M{"$gt": 42}}, M{"$inc": M{"n": 1}})
543 c.Assert(err, IsNil)
544 c.Assert(info.Updated, Equals, 4)
545
546 result := make(M)
547 err = coll.Find(M{"k": 42}).One(result)
548 c.Assert(err, IsNil)
549 c.Assert(result["n"], Equals, 42)
550
551 err = coll.Find(M{"k": 43}).One(result)
552 c.Assert(err, IsNil)
553 c.Assert(result["n"], Equals, 44)
554
555 err = coll.Find(M{"k": 44}).One(result)
556 c.Assert(err, IsNil)
557 c.Assert(result["n"], Equals, 45)
558
559 if !s.versionAtLeast(2, 6) {
560 // 2.6 made this invalid.
561 info, err = coll.UpdateAll(M{"k": 47}, M{"k": 47, "n": 47})
562 c.Assert(err, Equals, nil)
563 c.Assert(info.Updated, Equals, 0)
564 }
565}
566
567func (s *S) TestRemove(c *C) {
568 session, err := mgo.Dial("localhost:40001")
569 c.Assert(err, IsNil)
570 defer session.Close()
571
572 coll := session.DB("mydb").C("mycoll")
573
574 ns := []int{40, 41, 42, 43, 44, 45, 46}
575 for _, n := range ns {
576 err := coll.Insert(M{"n": n})
577 c.Assert(err, IsNil)
578 }
579
580 err = coll.Remove(M{"n": M{"$gt": 42}})
581 c.Assert(err, IsNil)
582
583 result := &struct{ N int }{}
584 err = coll.Find(M{"n": 42}).One(result)
585 c.Assert(err, IsNil)
586 c.Assert(result.N, Equals, 42)
587
588 err = coll.Find(M{"n": 43}).One(result)
589 c.Assert(err, Equals, mgo.ErrNotFound)
590
591 err = coll.Find(M{"n": 44}).One(result)
592 c.Assert(err, IsNil)
593 c.Assert(result.N, Equals, 44)
594}
595
596func (s *S) TestRemoveId(c *C) {
597 session, err := mgo.Dial("localhost:40001")
598 c.Assert(err, IsNil)
599 defer session.Close()
600
601 coll := session.DB("mydb").C("mycoll")
602
603 err = coll.Insert(M{"_id": 40}, M{"_id": 41}, M{"_id": 42})
604 c.Assert(err, IsNil)
605
606 err = coll.RemoveId(41)
607 c.Assert(err, IsNil)
608
609 c.Assert(coll.FindId(40).One(nil), IsNil)
610 c.Assert(coll.FindId(41).One(nil), Equals, mgo.ErrNotFound)
611 c.Assert(coll.FindId(42).One(nil), IsNil)
612}
613
614func (s *S) TestRemoveUnsafe(c *C) {
615 session, err := mgo.Dial("localhost:40001")
616 c.Assert(err, IsNil)
617 defer session.Close()
618
619 session.SetSafe(nil)
620
621 coll := session.DB("mydb").C("mycoll")
622
623 err = coll.Insert(M{"_id": 40}, M{"_id": 41}, M{"_id": 42})
624 c.Assert(err, IsNil)
625
626 err = coll.RemoveId(41)
627 c.Assert(err, IsNil)
628
629 c.Assert(coll.FindId(40).One(nil), IsNil)
630 c.Assert(coll.FindId(41).One(nil), Equals, mgo.ErrNotFound)
631 c.Assert(coll.FindId(42).One(nil), IsNil)
632}
633
634func (s *S) TestRemoveAll(c *C) {
635 session, err := mgo.Dial("localhost:40001")
636 c.Assert(err, IsNil)
637 defer session.Close()
638
639 coll := session.DB("mydb").C("mycoll")
640
641 ns := []int{40, 41, 42, 43, 44, 45, 46}
642 for _, n := range ns {
643 err := coll.Insert(M{"n": n})
644 c.Assert(err, IsNil)
645 }
646
647 info, err := coll.RemoveAll(M{"n": M{"$gt": 42}})
648 c.Assert(err, IsNil)
649 c.Assert(info.Updated, Equals, 0)
650 c.Assert(info.Removed, Equals, 4)
651 c.Assert(info.UpsertedId, IsNil)
652
653 result := &struct{ N int }{}
654 err = coll.Find(M{"n": 42}).One(result)
655 c.Assert(err, IsNil)
656 c.Assert(result.N, Equals, 42)
657
658 err = coll.Find(M{"n": 43}).One(result)
659 c.Assert(err, Equals, mgo.ErrNotFound)
660
661 err = coll.Find(M{"n": 44}).One(result)
662 c.Assert(err, Equals, mgo.ErrNotFound)
663
664 info, err = coll.RemoveAll(nil)
665 c.Assert(err, IsNil)
666 c.Assert(info.Updated, Equals, 0)
667 c.Assert(info.Removed, Equals, 3)
668 c.Assert(info.UpsertedId, IsNil)
669
670 n, err := coll.Find(nil).Count()
671 c.Assert(err, IsNil)
672 c.Assert(n, Equals, 0)
673}
674
675func (s *S) TestDropDatabase(c *C) {
676 session, err := mgo.Dial("localhost:40001")
677 c.Assert(err, IsNil)
678 defer session.Close()
679
680 db1 := session.DB("db1")
681 db1.C("col").Insert(M{"_id": 1})
682
683 db2 := session.DB("db2")
684 db2.C("col").Insert(M{"_id": 1})
685
686 err = db1.DropDatabase()
687 c.Assert(err, IsNil)
688
689 names, err := session.DatabaseNames()
690 c.Assert(err, IsNil)
691 if !reflect.DeepEqual(names, []string{"db2"}) {
692 // 2.4+ has "local" as well.
693 c.Assert(names, DeepEquals, []string{"db2", "local"})
694 }
695
696 err = db2.DropDatabase()
697 c.Assert(err, IsNil)
698
699 names, err = session.DatabaseNames()
700 c.Assert(err, IsNil)
701 if !reflect.DeepEqual(names, []string(nil)) {
702 // 2.4+ has "local" as well.
703 c.Assert(names, DeepEquals, []string{"local"})
704 }
705}
706
707func (s *S) TestDropCollection(c *C) {
708 session, err := mgo.Dial("localhost:40001")
709 c.Assert(err, IsNil)
710 defer session.Close()
711
712 db := session.DB("db1")
713 db.C("col1").Insert(M{"_id": 1})
714 db.C("col2").Insert(M{"_id": 1})
715
716 err = db.C("col1").DropCollection()
717 c.Assert(err, IsNil)
718
719 names, err := db.CollectionNames()
720 c.Assert(err, IsNil)
721 c.Assert(names, DeepEquals, []string{"col2", "system.indexes"})
722
723 err = db.C("col2").DropCollection()
724 c.Assert(err, IsNil)
725
726 names, err = db.CollectionNames()
727 c.Assert(err, IsNil)
728 c.Assert(names, DeepEquals, []string{"system.indexes"})
729}
730
731func (s *S) TestCreateCollectionCapped(c *C) {
732 session, err := mgo.Dial("localhost:40001")
733 c.Assert(err, IsNil)
734 defer session.Close()
735
736 coll := session.DB("mydb").C("mycoll")
737
738 info := &mgo.CollectionInfo{
739 Capped: true,
740 MaxBytes: 1024,
741 MaxDocs: 3,
742 }
743 err = coll.Create(info)
744 c.Assert(err, IsNil)
745
746 ns := []int{1, 2, 3, 4, 5}
747 for _, n := range ns {
748 err := coll.Insert(M{"n": n})
749 c.Assert(err, IsNil)
750 }
751
752 n, err := coll.Find(nil).Count()
753 c.Assert(err, IsNil)
754 c.Assert(n, Equals, 3)
755}
756
757func (s *S) TestCreateCollectionNoIndex(c *C) {
758 session, err := mgo.Dial("localhost:40001")
759 c.Assert(err, IsNil)
760 defer session.Close()
761
762 coll := session.DB("mydb").C("mycoll")
763
764 info := &mgo.CollectionInfo{
765 DisableIdIndex: true,
766 }
767 err = coll.Create(info)
768 c.Assert(err, IsNil)
769
770 err = coll.Insert(M{"n": 1})
771 c.Assert(err, IsNil)
772
773 indexes, err := coll.Indexes()
774 c.Assert(indexes, HasLen, 0)
775}
776
777func (s *S) TestCreateCollectionForceIndex(c *C) {
778 session, err := mgo.Dial("localhost:40001")
779 c.Assert(err, IsNil)
780 defer session.Close()
781
782 coll := session.DB("mydb").C("mycoll")
783
784 info := &mgo.CollectionInfo{
785 ForceIdIndex: true,
786 Capped: true,
787 MaxBytes: 1024,
788 }
789 err = coll.Create(info)
790 c.Assert(err, IsNil)
791
792 err = coll.Insert(M{"n": 1})
793 c.Assert(err, IsNil)
794
795 indexes, err := coll.Indexes()
796 c.Assert(indexes, HasLen, 1)
797}
798
799func (s *S) TestIsDupValues(c *C) {
800 c.Assert(mgo.IsDup(nil), Equals, false)
801 c.Assert(mgo.IsDup(&mgo.LastError{Code: 1}), Equals, false)
802 c.Assert(mgo.IsDup(&mgo.QueryError{Code: 1}), Equals, false)
803 c.Assert(mgo.IsDup(&mgo.LastError{Code: 11000}), Equals, true)
804 c.Assert(mgo.IsDup(&mgo.QueryError{Code: 11000}), Equals, true)
805 c.Assert(mgo.IsDup(&mgo.LastError{Code: 11001}), Equals, true)
806 c.Assert(mgo.IsDup(&mgo.QueryError{Code: 11001}), Equals, true)
807 c.Assert(mgo.IsDup(&mgo.LastError{Code: 12582}), Equals, true)
808 c.Assert(mgo.IsDup(&mgo.QueryError{Code: 12582}), Equals, true)
809 lerr := &mgo.LastError{Code: 16460, Err: "error inserting 1 documents to shard ... caused by :: E11000 duplicate key error index: ..."}
810 c.Assert(mgo.IsDup(lerr), Equals, true)
811}
812
813func (s *S) TestIsDupPrimary(c *C) {
814 session, err := mgo.Dial("localhost:40001")
815 c.Assert(err, IsNil)
816 defer session.Close()
817
818 coll := session.DB("mydb").C("mycoll")
819
820 err = coll.Insert(M{"_id": 1})
821 c.Assert(err, IsNil)
822 err = coll.Insert(M{"_id": 1})
823 c.Assert(err, ErrorMatches, ".*duplicate key error.*")
824 c.Assert(mgo.IsDup(err), Equals, true)
825}
826
827func (s *S) TestIsDupUnique(c *C) {
828 session, err := mgo.Dial("localhost:40001")
829 c.Assert(err, IsNil)
830 defer session.Close()
831
832 index := mgo.Index{
833 Key: []string{"a", "b"},
834 Unique: true,
835 }
836
837 coll := session.DB("mydb").C("mycoll")
838
839 err = coll.EnsureIndex(index)
840 c.Assert(err, IsNil)
841
842 err = coll.Insert(M{"a": 1, "b": 1})
843 c.Assert(err, IsNil)
844 err = coll.Insert(M{"a": 1, "b": 1})
845 c.Assert(err, ErrorMatches, ".*duplicate key error.*")
846 c.Assert(mgo.IsDup(err), Equals, true)
847}
848
849func (s *S) TestIsDupCapped(c *C) {
850 session, err := mgo.Dial("localhost:40001")
851 c.Assert(err, IsNil)
852 defer session.Close()
853
854 coll := session.DB("mydb").C("mycoll")
855
856 info := &mgo.CollectionInfo{
857 ForceIdIndex: true,
858 Capped: true,
859 MaxBytes: 1024,
860 }
861 err = coll.Create(info)
862 c.Assert(err, IsNil)
863
864 err = coll.Insert(M{"_id": 1})
865 c.Assert(err, IsNil)
866 err = coll.Insert(M{"_id": 1})
867 // The error was different for capped collections before 2.6.
868 c.Assert(err, ErrorMatches, ".*duplicate key.*")
869 // The issue is reduced by using IsDup.
870 c.Assert(mgo.IsDup(err), Equals, true)
871}
872
873func (s *S) TestIsDupFindAndModify(c *C) {
874 session, err := mgo.Dial("localhost:40001")
875 c.Assert(err, IsNil)
876 defer session.Close()
877
878 coll := session.DB("mydb").C("mycoll")
879
880 err = coll.EnsureIndex(mgo.Index{Key: []string{"n"}, Unique: true})
881 c.Assert(err, IsNil)
882
883 err = coll.Insert(M{"n": 1})
884 c.Assert(err, IsNil)
885 err = coll.Insert(M{"n": 2})
886 c.Assert(err, IsNil)
887 _, err = coll.Find(M{"n": 1}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}}, bson.M{})
888 c.Assert(err, ErrorMatches, ".*duplicate key error.*")
889 c.Assert(mgo.IsDup(err), Equals, true)
890}
891
892func (s *S) TestFindAndModify(c *C) {
893 session, err := mgo.Dial("localhost:40011")
894 c.Assert(err, IsNil)
895 defer session.Close()
896
897 coll := session.DB("mydb").C("mycoll")
898
899 err = coll.Insert(M{"n": 42})
900
901 session.SetMode(mgo.Monotonic, true)
902
903 result := M{}
904 info, err := coll.Find(M{"n": 42}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}}, result)
905 c.Assert(err, IsNil)
906 c.Assert(result["n"], Equals, 42)
907 c.Assert(info.Updated, Equals, 1)
908 c.Assert(info.Removed, Equals, 0)
909 c.Assert(info.UpsertedId, IsNil)
910
911 // A nil result parameter should be acceptable.
912 info, err = coll.Find(M{"n": 43}).Apply(mgo.Change{Update: M{"$unset": M{"missing": 1}}}, nil)
913 c.Assert(err, IsNil)
914 c.Assert(info.Updated, Equals, 1)
915 c.Assert(info.Removed, Equals, 0)
916 c.Assert(info.UpsertedId, IsNil)
917
918 result = M{}
919 info, err = coll.Find(M{"n": 43}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}, ReturnNew: true}, result)
920 c.Assert(err, IsNil)
921 c.Assert(result["n"], Equals, 44)
922 c.Assert(info.Updated, Equals, 1)
923 c.Assert(info.Removed, Equals, 0)
924 c.Assert(info.UpsertedId, IsNil)
925
926 result = M{}
927 info, err = coll.Find(M{"n": 50}).Apply(mgo.Change{Upsert: true, Update: M{"n": 51, "o": 52}}, result)
928 c.Assert(err, IsNil)
929 c.Assert(result["n"], IsNil)
930 c.Assert(info.Updated, Equals, 0)
931 c.Assert(info.Removed, Equals, 0)
932 c.Assert(info.UpsertedId, NotNil)
933
934 result = M{}
935 info, err = coll.Find(nil).Sort("-n").Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}, ReturnNew: true}, result)
936 c.Assert(err, IsNil)
937 c.Assert(result["n"], Equals, 52)
938 c.Assert(info.Updated, Equals, 1)
939 c.Assert(info.Removed, Equals, 0)
940 c.Assert(info.UpsertedId, IsNil)
941
942 result = M{}
943 info, err = coll.Find(M{"n": 52}).Select(M{"o": 1}).Apply(mgo.Change{Remove: true}, result)
944 c.Assert(err, IsNil)
945 c.Assert(result["n"], IsNil)
946 c.Assert(result["o"], Equals, 52)
947 c.Assert(info.Updated, Equals, 0)
948 c.Assert(info.Removed, Equals, 1)
949 c.Assert(info.UpsertedId, IsNil)
950
951 result = M{}
952 info, err = coll.Find(M{"n": 60}).Apply(mgo.Change{Remove: true}, result)
953 c.Assert(err, Equals, mgo.ErrNotFound)
954 c.Assert(len(result), Equals, 0)
955 c.Assert(info, IsNil)
956}
957
958func (s *S) TestFindAndModifyBug997828(c *C) {
959 session, err := mgo.Dial("localhost:40001")
960 c.Assert(err, IsNil)
961 defer session.Close()
962
963 coll := session.DB("mydb").C("mycoll")
964
965 err = coll.Insert(M{"n": "not-a-number"})
966
967 result := make(M)
968 _, err = coll.Find(M{"n": "not-a-number"}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}}, result)
969 c.Assert(err, ErrorMatches, `(exception: )?Cannot apply \$inc .*`)
970 if s.versionAtLeast(2, 1) {
971 qerr, _ := err.(*mgo.QueryError)
972 c.Assert(qerr, NotNil, Commentf("err: %#v", err))
973 if s.versionAtLeast(2, 6) {
974 // Oh, the dance of error codes. :-(
975 c.Assert(qerr.Code, Equals, 16837)
976 } else {
977 c.Assert(qerr.Code, Equals, 10140)
978 }
979 } else {
980 lerr, _ := err.(*mgo.LastError)
981 c.Assert(lerr, NotNil, Commentf("err: %#v", err))
982 c.Assert(lerr.Code, Equals, 10140)
983 }
984}
985
986func (s *S) TestCountCollection(c *C) {
987 session, err := mgo.Dial("localhost:40001")
988 c.Assert(err, IsNil)
989 defer session.Close()
990
991 coll := session.DB("mydb").C("mycoll")
992
993 ns := []int{40, 41, 42}
994 for _, n := range ns {
995 err := coll.Insert(M{"n": n})
996 c.Assert(err, IsNil)
997 }
998
999 n, err := coll.Count()
1000 c.Assert(err, IsNil)
1001 c.Assert(n, Equals, 3)
1002}
1003
1004func (s *S) TestCountQuery(c *C) {
1005 session, err := mgo.Dial("localhost:40001")
1006 c.Assert(err, IsNil)
1007 defer session.Close()
1008
1009 coll := session.DB("mydb").C("mycoll")
1010
1011 ns := []int{40, 41, 42}
1012 for _, n := range ns {
1013 err := coll.Insert(M{"n": n})
1014 c.Assert(err, IsNil)
1015 }
1016
1017 n, err := coll.Find(M{"n": M{"$gt": 40}}).Count()
1018 c.Assert(err, IsNil)
1019 c.Assert(n, Equals, 2)
1020}
1021
1022func (s *S) TestCountQuerySorted(c *C) {
1023 session, err := mgo.Dial("localhost:40001")
1024 c.Assert(err, IsNil)
1025 defer session.Close()
1026
1027 coll := session.DB("mydb").C("mycoll")
1028
1029 ns := []int{40, 41, 42}
1030 for _, n := range ns {
1031 err := coll.Insert(M{"n": n})
1032 c.Assert(err, IsNil)
1033 }
1034
1035 n, err := coll.Find(M{"n": M{"$gt": 40}}).Sort("n").Count()
1036 c.Assert(err, IsNil)
1037 c.Assert(n, Equals, 2)
1038}
1039
1040func (s *S) TestCountSkipLimit(c *C) {
1041 session, err := mgo.Dial("localhost:40001")
1042 c.Assert(err, IsNil)
1043 defer session.Close()
1044
1045 coll := session.DB("mydb").C("mycoll")
1046
1047 ns := []int{40, 41, 42, 43, 44}
1048 for _, n := range ns {
1049 err := coll.Insert(M{"n": n})
1050 c.Assert(err, IsNil)
1051 }
1052
1053 n, err := coll.Find(nil).Skip(1).Limit(3).Count()
1054 c.Assert(err, IsNil)
1055 c.Assert(n, Equals, 3)
1056
1057 n, err = coll.Find(nil).Skip(1).Limit(5).Count()
1058 c.Assert(err, IsNil)
1059 c.Assert(n, Equals, 4)
1060}
1061
1062func (s *S) TestQueryExplain(c *C) {
1063 session, err := mgo.Dial("localhost:40001")
1064 c.Assert(err, IsNil)
1065 defer session.Close()
1066
1067 coll := session.DB("mydb").C("mycoll")
1068
1069 ns := []int{40, 41, 42}
1070 for _, n := range ns {
1071 err := coll.Insert(M{"n": n})
1072 c.Assert(err, IsNil)
1073 }
1074
1075 m := M{}
1076 query := coll.Find(nil).Limit(2)
1077 err = query.Explain(m)
1078 c.Assert(err, IsNil)
1079 if m["queryPlanner"] != nil {
1080 c.Assert(m["executionStats"].(M)["totalDocsExamined"], Equals, 2)
1081 } else {
1082 c.Assert(m["cursor"], Equals, "BasicCursor")
1083 c.Assert(m["nscanned"], Equals, 2)
1084 c.Assert(m["n"], Equals, 2)
1085 }
1086
1087 n := 0
1088 var result M
1089 iter := query.Iter()
1090 for iter.Next(&result) {
1091 n++
1092 }
1093 c.Assert(iter.Close(), IsNil)
1094 c.Assert(n, Equals, 2)
1095}
1096
1097func (s *S) TestQuerySetMaxScan(c *C) {
1098 session, err := mgo.Dial("localhost:40001")
1099 c.Assert(err, IsNil)
1100 defer session.Close()
1101 coll := session.DB("mydb").C("mycoll")
1102
1103 ns := []int{40, 41, 42}
1104 for _, n := range ns {
1105 err := coll.Insert(M{"n": n})
1106 c.Assert(err, IsNil)
1107 }
1108
1109 query := coll.Find(nil).SetMaxScan(2)
1110 var result []M
1111 err = query.All(&result)
1112 c.Assert(err, IsNil)
1113 c.Assert(result, HasLen, 2)
1114}
1115
1116func (s *S) TestQuerySetMaxTime(c *C) {
1117 if !s.versionAtLeast(2, 6) {
1118 c.Skip("SetMaxTime only supported in 2.6+")
1119 }
1120
1121 session, err := mgo.Dial("localhost:40001")
1122 c.Assert(err, IsNil)
1123 defer session.Close()
1124 coll := session.DB("mydb").C("mycoll")
1125
1126 for i := 0; i < 1000; i++ {
1127 err := coll.Insert(M{"n": i})
1128 c.Assert(err, IsNil)
1129 }
1130
1131 query := coll.Find(nil)
1132 query.SetMaxTime(1 * time.Millisecond)
1133 query.Batch(2)
1134 var result []M
1135 err = query.All(&result)
1136 c.Assert(err, ErrorMatches, "operation exceeded time limit")
1137}
1138
1139func (s *S) TestQueryHint(c *C) {
1140 session, err := mgo.Dial("localhost:40001")
1141 c.Assert(err, IsNil)
1142 defer session.Close()
1143
1144 coll := session.DB("mydb").C("mycoll")
1145 coll.EnsureIndexKey("a")
1146
1147 m := M{}
1148 err = coll.Find(nil).Hint("a").Explain(m)
1149 c.Assert(err, IsNil)
1150
1151 if m["queryPlanner"] != nil {
1152 m = m["queryPlanner"].(M)
1153 m = m["winningPlan"].(M)
1154 m = m["inputStage"].(M)
1155 c.Assert(m["indexName"], Equals, "a_1")
1156 } else {
1157 c.Assert(m["indexBounds"], NotNil)
1158 c.Assert(m["indexBounds"].(M)["a"], NotNil)
1159 }
1160}
1161
1162func (s *S) TestQueryComment(c *C) {
1163 session, err := mgo.Dial("localhost:40001")
1164 c.Assert(err, IsNil)
1165 defer session.Close()
1166
1167 db := session.DB("mydb")
1168 coll := db.C("mycoll")
1169
1170 err = db.Run(bson.M{"profile": 2}, nil)
1171 c.Assert(err, IsNil)
1172
1173 ns := []int{40, 41, 42}
1174 for _, n := range ns {
1175 err := coll.Insert(M{"n": n})
1176 c.Assert(err, IsNil)
1177 }
1178
1179 query := coll.Find(bson.M{"n": 41})
1180 query.Comment("some comment")
1181 err = query.One(nil)
1182 c.Assert(err, IsNil)
1183
1184 query = coll.Find(bson.M{"n": 41})
1185 query.Comment("another comment")
1186 err = query.One(nil)
1187 c.Assert(err, IsNil)
1188
1189 n, err := session.DB("mydb").C("system.profile").Find(bson.M{"query.$query.n": 41, "query.$comment": "some comment"}).Count()
1190 c.Assert(err, IsNil)
1191 c.Assert(n, Equals, 1)
1192}
1193
1194func (s *S) TestFindOneNotFound(c *C) {
1195 session, err := mgo.Dial("localhost:40001")
1196 c.Assert(err, IsNil)
1197 defer session.Close()
1198
1199 coll := session.DB("mydb").C("mycoll")
1200
1201 result := struct{ A, B int }{}
1202 err = coll.Find(M{"a": 1}).One(&result)
1203 c.Assert(err, Equals, mgo.ErrNotFound)
1204 c.Assert(err, ErrorMatches, "not found")
1205 c.Assert(err == mgo.ErrNotFound, Equals, true)
1206}
1207
1208func (s *S) TestFindNil(c *C) {
1209 session, err := mgo.Dial("localhost:40001")
1210 c.Assert(err, IsNil)
1211 defer session.Close()
1212
1213 coll := session.DB("mydb").C("mycoll")
1214 err = coll.Insert(M{"n": 1})
1215 c.Assert(err, IsNil)
1216
1217 result := struct{ N int }{}
1218
1219 err = coll.Find(nil).One(&result)
1220 c.Assert(err, IsNil)
1221 c.Assert(result.N, Equals, 1)
1222}
1223
1224func (s *S) TestFindId(c *C) {
1225 session, err := mgo.Dial("localhost:40001")
1226 c.Assert(err, IsNil)
1227 defer session.Close()
1228
1229 coll := session.DB("mydb").C("mycoll")
1230 err = coll.Insert(M{"_id": 41, "n": 41})
1231 c.Assert(err, IsNil)
1232 err = coll.Insert(M{"_id": 42, "n": 42})
1233 c.Assert(err, IsNil)
1234
1235 result := struct{ N int }{}
1236
1237 err = coll.FindId(42).One(&result)
1238 c.Assert(err, IsNil)
1239 c.Assert(result.N, Equals, 42)
1240}
1241
1242func (s *S) TestFindIterAll(c *C) {
1243 session, err := mgo.Dial("localhost:40001")
1244 c.Assert(err, IsNil)
1245 defer session.Close()
1246
1247 coll := session.DB("mydb").C("mycoll")
1248
1249 ns := []int{40, 41, 42, 43, 44, 45, 46}
1250 for _, n := range ns {
1251 coll.Insert(M{"n": n})
1252 }
1253
1254 session.Refresh() // Release socket.
1255
1256 mgo.ResetStats()
1257
1258 iter := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2).Iter()
1259 result := struct{ N int }{}
1260 for i := 2; i < 7; i++ {
1261 ok := iter.Next(&result)
1262 c.Assert(ok, Equals, true)
1263 c.Assert(result.N, Equals, ns[i])
1264 if i == 1 {
1265 stats := mgo.GetStats()
1266 c.Assert(stats.ReceivedDocs, Equals, 2)
1267 }
1268 }
1269
1270 ok := iter.Next(&result)
1271 c.Assert(ok, Equals, false)
1272 c.Assert(iter.Close(), IsNil)
1273
1274 session.Refresh() // Release socket.
1275
1276 stats := mgo.GetStats()
1277 c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
1278 c.Assert(stats.ReceivedOps, Equals, 3) // and their REPLY_OPs.
1279 c.Assert(stats.ReceivedDocs, Equals, 5)
1280 c.Assert(stats.SocketsInUse, Equals, 0)
1281}
1282
1283func (s *S) TestFindIterTwiceWithSameQuery(c *C) {
1284 session, err := mgo.Dial("localhost:40001")
1285 c.Assert(err, IsNil)
1286 defer session.Close()
1287
1288 coll := session.DB("mydb").C("mycoll")
1289
1290 for i := 40; i != 47; i++ {
1291 coll.Insert(M{"n": i})
1292 }
1293
1294 query := coll.Find(M{}).Sort("n")
1295
1296 result1 := query.Skip(1).Iter()
1297 result2 := query.Skip(2).Iter()
1298
1299 result := struct{ N int }{}
1300 ok := result2.Next(&result)
1301 c.Assert(ok, Equals, true)
1302 c.Assert(result.N, Equals, 42)
1303 ok = result1.Next(&result)
1304 c.Assert(ok, Equals, true)
1305 c.Assert(result.N, Equals, 41)
1306}
1307
1308func (s *S) TestFindIterWithoutResults(c *C) {
1309 session, err := mgo.Dial("localhost:40001")
1310 c.Assert(err, IsNil)
1311 defer session.Close()
1312
1313 coll := session.DB("mydb").C("mycoll")
1314 coll.Insert(M{"n": 42})
1315
1316 iter := coll.Find(M{"n": 0}).Iter()
1317
1318 result := struct{ N int }{}
1319 ok := iter.Next(&result)
1320 c.Assert(ok, Equals, false)
1321 c.Assert(iter.Close(), IsNil)
1322 c.Assert(result.N, Equals, 0)
1323}
1324
1325func (s *S) TestFindIterLimit(c *C) {
1326 session, err := mgo.Dial("localhost:40001")
1327 c.Assert(err, IsNil)
1328 defer session.Close()
1329
1330 coll := session.DB("mydb").C("mycoll")
1331
1332 ns := []int{40, 41, 42, 43, 44, 45, 46}
1333 for _, n := range ns {
1334 coll.Insert(M{"n": n})
1335 }
1336
1337 session.Refresh() // Release socket.
1338
1339 mgo.ResetStats()
1340
1341 query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Limit(3)
1342 iter := query.Iter()
1343
1344 result := struct{ N int }{}
1345 for i := 2; i < 5; i++ {
1346 ok := iter.Next(&result)
1347 c.Assert(ok, Equals, true)
1348 c.Assert(result.N, Equals, ns[i])
1349 }
1350
1351 ok := iter.Next(&result)
1352 c.Assert(ok, Equals, false)
1353 c.Assert(iter.Close(), IsNil)
1354
1355 session.Refresh() // Release socket.
1356
1357 stats := mgo.GetStats()
1358 c.Assert(stats.SentOps, Equals, 2) // 1*QUERY_OP + 1*KILL_CURSORS_OP
1359 c.Assert(stats.ReceivedOps, Equals, 1) // and its REPLY_OP
1360 c.Assert(stats.ReceivedDocs, Equals, 3)
1361 c.Assert(stats.SocketsInUse, Equals, 0)
1362}
1363
1364var cursorTimeout = flag.Bool("cursor-timeout", false, "Enable cursor timeout test")
1365
1366func (s *S) TestFindIterCursorTimeout(c *C) {
1367 if !*cursorTimeout {
1368 c.Skip("-cursor-timeout")
1369 }
1370 session, err := mgo.Dial("localhost:40001")
1371 c.Assert(err, IsNil)
1372 defer session.Close()
1373
1374 type Doc struct {
1375 Id int "_id"
1376 }
1377
1378 coll := session.DB("test").C("test")
1379 coll.Remove(nil)
1380 for i := 0; i < 100; i++ {
1381 err = coll.Insert(Doc{i})
1382 c.Assert(err, IsNil)
1383 }
1384
1385 session.SetBatch(1)
1386 iter := coll.Find(nil).Iter()
1387 var doc Doc
1388 if !iter.Next(&doc) {
1389 c.Fatalf("iterator failed to return any documents")
1390 }
1391
1392 for i := 10; i > 0; i-- {
1393 c.Logf("Sleeping... %d minutes to go...", i)
1394 time.Sleep(1*time.Minute + 2*time.Second)
1395 }
1396
1397 // Drain any existing documents that were fetched.
1398 if !iter.Next(&doc) {
1399 c.Fatalf("iterator with timed out cursor failed to return previously cached document")
1400 }
1401 if iter.Next(&doc) {
1402 c.Fatalf("timed out cursor returned document")
1403 }
1404
1405 c.Assert(iter.Err(), Equals, mgo.ErrCursor)
1406}
1407
1408func (s *S) TestTooManyItemsLimitBug(c *C) {
1409 if *fast {
1410 c.Skip("-fast")
1411 }
1412
1413 session, err := mgo.Dial("localhost:40001")
1414 c.Assert(err, IsNil)
1415 defer session.Close()
1416 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
1417
1418 mgo.SetDebug(false)
1419 coll := session.DB("mydb").C("mycoll")
1420 words := strings.Split("foo bar baz", " ")
1421 for i := 0; i < 5; i++ {
1422 words = append(words, words...)
1423 }
1424 doc := bson.D{{"words", words}}
1425 inserts := 10000
1426 limit := 5000
1427 iters := 0
1428 c.Assert(inserts > limit, Equals, true)
1429 for i := 0; i < inserts; i++ {
1430 err := coll.Insert(&doc)
1431 c.Assert(err, IsNil)
1432 }
1433 iter := coll.Find(nil).Limit(limit).Iter()
1434 for iter.Next(&doc) {
1435 if iters%100 == 0 {
1436 c.Logf("Seen %d docments", iters)
1437 }
1438 iters++
1439 }
1440 c.Assert(iter.Close(), IsNil)
1441 c.Assert(iters, Equals, limit)
1442}
1443
1444func serverCursorsOpen(session *mgo.Session) int {
1445 var result struct {
1446 Cursors struct {
1447 TotalOpen int `bson:"totalOpen"`
1448 TimedOut int `bson:"timedOut"`
1449 }
1450 }
1451 err := session.Run("serverStatus", &result)
1452 if err != nil {
1453 panic(err)
1454 }
1455 return result.Cursors.TotalOpen
1456}
1457
1458func (s *S) TestFindIterLimitWithMore(c *C) {
1459 session, err := mgo.Dial("localhost:40001")
1460 c.Assert(err, IsNil)
1461 defer session.Close()
1462
1463 coll := session.DB("mydb").C("mycoll")
1464
1465 // Insane amounts of logging otherwise due to the
1466 // amount of data being shuffled.
1467 mgo.SetDebug(false)
1468 defer mgo.SetDebug(true)
1469
1470 // Should amount to more than 4MB bson payload,
1471 // the default limit per result chunk.
1472 const total = 4096
1473 var d struct{ A [1024]byte }
1474 docs := make([]interface{}, total)
1475 for i := 0; i < total; i++ {
1476 docs[i] = &d
1477 }
1478 err = coll.Insert(docs...)
1479 c.Assert(err, IsNil)
1480
1481 n, err := coll.Count()
1482 c.Assert(err, IsNil)
1483 c.Assert(n, Equals, total)
1484
1485 // First, try restricting to a single chunk with a negative limit.
1486 nresults := 0
1487 iter := coll.Find(nil).Limit(-total).Iter()
1488 var discard struct{}
1489 for iter.Next(&discard) {
1490 nresults++
1491 }
1492 if nresults < total/2 || nresults >= total {
1493 c.Fatalf("Bad result size with negative limit: %d", nresults)
1494 }
1495
1496 cursorsOpen := serverCursorsOpen(session)
1497
1498 // Try again, with a positive limit. Should reach the end now,
1499 // using multiple chunks.
1500 nresults = 0
1501 iter = coll.Find(nil).Limit(total).Iter()
1502 for iter.Next(&discard) {
1503 nresults++
1504 }
1505 c.Assert(nresults, Equals, total)
1506
1507 // Ensure the cursor used is properly killed.
1508 c.Assert(serverCursorsOpen(session), Equals, cursorsOpen)
1509
1510 // Edge case, -MinInt == -MinInt.
1511 nresults = 0
1512 iter = coll.Find(nil).Limit(math.MinInt32).Iter()
1513 for iter.Next(&discard) {
1514 nresults++
1515 }
1516 if nresults < total/2 || nresults >= total {
1517 c.Fatalf("Bad result size with MinInt32 limit: %d", nresults)
1518 }
1519}
1520
1521func (s *S) TestFindIterLimitWithBatch(c *C) {
1522 session, err := mgo.Dial("localhost:40001")
1523 c.Assert(err, IsNil)
1524 defer session.Close()
1525
1526 coll := session.DB("mydb").C("mycoll")
1527
1528 ns := []int{40, 41, 42, 43, 44, 45, 46}
1529 for _, n := range ns {
1530 coll.Insert(M{"n": n})
1531 }
1532
1533 // Ping the database to ensure the nonce has been received already.
1534 c.Assert(session.Ping(), IsNil)
1535
1536 session.Refresh() // Release socket.
1537
1538 mgo.ResetStats()
1539
1540 query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Limit(3).Batch(2)
1541 iter := query.Iter()
1542 result := struct{ N int }{}
1543 for i := 2; i < 5; i++ {
1544 ok := iter.Next(&result)
1545 c.Assert(ok, Equals, true)
1546 c.Assert(result.N, Equals, ns[i])
1547 if i == 3 {
1548 stats := mgo.GetStats()
1549 c.Assert(stats.ReceivedDocs, Equals, 2)
1550 }
1551 }
1552
1553 ok := iter.Next(&result)
1554 c.Assert(ok, Equals, false)
1555 c.Assert(iter.Close(), IsNil)
1556
1557 session.Refresh() // Release socket.
1558
1559 stats := mgo.GetStats()
1560 c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 1*GET_MORE_OP + 1*KILL_CURSORS_OP
1561 c.Assert(stats.ReceivedOps, Equals, 2) // and its REPLY_OPs
1562 c.Assert(stats.ReceivedDocs, Equals, 3)
1563 c.Assert(stats.SocketsInUse, Equals, 0)
1564}
1565
1566func (s *S) TestFindIterSortWithBatch(c *C) {
1567 session, err := mgo.Dial("localhost:40001")
1568 c.Assert(err, IsNil)
1569 defer session.Close()
1570
1571 coll := session.DB("mydb").C("mycoll")
1572
1573 ns := []int{40, 41, 42, 43, 44, 45, 46}
1574 for _, n := range ns {
1575 coll.Insert(M{"n": n})
1576 }
1577
1578 // Without this, the logic above breaks because Mongo refuses to
1579 // return a cursor with an in-memory sort.
1580 coll.EnsureIndexKey("n")
1581
1582 // Ping the database to ensure the nonce has been received already.
1583 c.Assert(session.Ping(), IsNil)
1584
1585 session.Refresh() // Release socket.
1586
1587 mgo.ResetStats()
1588
1589 query := coll.Find(M{"n": M{"$lte": 44}}).Sort("-n").Batch(2)
1590 iter := query.Iter()
1591 ns = []int{46, 45, 44, 43, 42, 41, 40}
1592 result := struct{ N int }{}
1593 for i := 2; i < len(ns); i++ {
1594 c.Logf("i=%d", i)
1595 ok := iter.Next(&result)
1596 c.Assert(ok, Equals, true)
1597 c.Assert(result.N, Equals, ns[i])
1598 if i == 3 {
1599 stats := mgo.GetStats()
1600 c.Assert(stats.ReceivedDocs, Equals, 2)
1601 }
1602 }
1603
1604 ok := iter.Next(&result)
1605 c.Assert(ok, Equals, false)
1606 c.Assert(iter.Close(), IsNil)
1607
1608 session.Refresh() // Release socket.
1609
1610 stats := mgo.GetStats()
1611 c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
1612 c.Assert(stats.ReceivedOps, Equals, 3) // and its REPLY_OPs
1613 c.Assert(stats.ReceivedDocs, Equals, 5)
1614 c.Assert(stats.SocketsInUse, Equals, 0)
1615}
1616
1617// Test tailable cursors in a situation where Next has to sleep to
1618// respect the timeout requested on Tail.
1619func (s *S) TestFindTailTimeoutWithSleep(c *C) {
1620 if *fast {
1621 c.Skip("-fast")
1622 }
1623
1624 session, err := mgo.Dial("localhost:40001")
1625 c.Assert(err, IsNil)
1626 defer session.Close()
1627
1628 cresult := struct{ ErrMsg string }{}
1629
1630 db := session.DB("mydb")
1631 err = db.Run(bson.D{{"create", "mycoll"}, {"capped", true}, {"size", 1024}}, &cresult)
1632 c.Assert(err, IsNil)
1633 c.Assert(cresult.ErrMsg, Equals, "")
1634 coll := db.C("mycoll")
1635
1636 ns := []int{40, 41, 42, 43, 44, 45, 46}
1637 for _, n := range ns {
1638 coll.Insert(M{"n": n})
1639 }
1640
1641 session.Refresh() // Release socket.
1642
1643 mgo.ResetStats()
1644
1645 timeout := 3 * time.Second
1646
1647 query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
1648 iter := query.Tail(timeout)
1649
1650 n := len(ns)
1651 result := struct{ N int }{}
1652 for i := 2; i != n; i++ {
1653 ok := iter.Next(&result)
1654 c.Assert(ok, Equals, true)
1655 c.Assert(iter.Err(), IsNil)
1656 c.Assert(iter.Timeout(), Equals, false)
1657 c.Assert(result.N, Equals, ns[i])
1658 if i == 3 { // The batch boundary.
1659 stats := mgo.GetStats()
1660 c.Assert(stats.ReceivedDocs, Equals, 2)
1661 }
1662 }
1663
1664 mgo.ResetStats()
1665
1666 // The following call to Next will block.
1667 go func() {
1668 // The internal AwaitData timing of MongoDB is around 2 seconds,
1669 // so this should force mgo to sleep at least once by itself to
1670 // respect the requested timeout.
1671 time.Sleep(timeout + 5e8*time.Nanosecond)
1672 session := session.New()
1673 defer session.Close()
1674 coll := session.DB("mydb").C("mycoll")
1675 coll.Insert(M{"n": 47})
1676 }()
1677
1678 c.Log("Will wait for Next with N=47...")
1679 ok := iter.Next(&result)
1680 c.Assert(ok, Equals, true)
1681 c.Assert(iter.Err(), IsNil)
1682 c.Assert(iter.Timeout(), Equals, false)
1683 c.Assert(result.N, Equals, 47)
1684 c.Log("Got Next with N=47!")
1685
1686 // The following may break because it depends a bit on the internal
1687 // timing used by MongoDB's AwaitData logic. If it does, the problem
1688 // will be observed as more GET_MORE_OPs than predicted:
1689 // 1*QUERY for nonce + 1*GET_MORE_OP on Next + 1*GET_MORE_OP on Next after sleep +
1690 // 1*INSERT_OP + 1*QUERY_OP for getLastError on insert of 47
1691 stats := mgo.GetStats()
1692 if s.versionAtLeast(3, 0) { // TODO Will be 2.6 when write commands are enabled for it.
1693 c.Assert(stats.SentOps, Equals, 4)
1694 } else {
1695 c.Assert(stats.SentOps, Equals, 5)
1696 }
1697 c.Assert(stats.ReceivedOps, Equals, 4) // REPLY_OPs for 1*QUERY_OP for nonce + 2*GET_MORE_OPs + 1*QUERY_OP
1698 c.Assert(stats.ReceivedDocs, Equals, 3) // nonce + N=47 result + getLastError response
1699
1700 c.Log("Will wait for a result which will never come...")
1701
1702 started := time.Now()
1703 ok = iter.Next(&result)
1704 c.Assert(ok, Equals, false)
1705 c.Assert(iter.Err(), IsNil)
1706 c.Assert(iter.Timeout(), Equals, true)
1707 c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
1708
1709 c.Log("Will now reuse the timed out tail cursor...")
1710
1711 coll.Insert(M{"n": 48})
1712 ok = iter.Next(&result)
1713 c.Assert(ok, Equals, true)
1714 c.Assert(iter.Close(), IsNil)
1715 c.Assert(iter.Timeout(), Equals, false)
1716 c.Assert(result.N, Equals, 48)
1717}
1718
1719// Test tailable cursors in a situation where Next never gets to sleep once
1720// to respect the timeout requested on Tail.
1721func (s *S) TestFindTailTimeoutNoSleep(c *C) {
1722 session, err := mgo.Dial("localhost:40001")
1723 c.Assert(err, IsNil)
1724 defer session.Close()
1725
1726 cresult := struct{ ErrMsg string }{}
1727
1728 db := session.DB("mydb")
1729 err = db.Run(bson.D{{"create", "mycoll"}, {"capped", true}, {"size", 1024}}, &cresult)
1730 c.Assert(err, IsNil)
1731 c.Assert(cresult.ErrMsg, Equals, "")
1732 coll := db.C("mycoll")
1733
1734 ns := []int{40, 41, 42, 43, 44, 45, 46}
1735 for _, n := range ns {
1736 coll.Insert(M{"n": n})
1737 }
1738
1739 session.Refresh() // Release socket.
1740
1741 mgo.ResetStats()
1742
1743 timeout := 1 * time.Second
1744
1745 query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
1746 iter := query.Tail(timeout)
1747
1748 n := len(ns)
1749 result := struct{ N int }{}
1750 for i := 2; i != n; i++ {
1751 ok := iter.Next(&result)
1752 c.Assert(ok, Equals, true)
1753 c.Assert(iter.Err(), IsNil)
1754 c.Assert(iter.Timeout(), Equals, false)
1755 c.Assert(result.N, Equals, ns[i])
1756 if i == 3 { // The batch boundary.
1757 stats := mgo.GetStats()
1758 c.Assert(stats.ReceivedDocs, Equals, 2)
1759 }
1760 }
1761
1762 mgo.ResetStats()
1763
1764 // The following call to Next will block.
1765 go func() {
1766 // The internal AwaitData timing of MongoDB is around 2 seconds,
1767 // so this item should arrive within the AwaitData threshold.
1768 time.Sleep(5e8)
1769 session := session.New()
1770 defer session.Close()
1771 coll := session.DB("mydb").C("mycoll")
1772 coll.Insert(M{"n": 47})
1773 }()
1774
1775 c.Log("Will wait for Next with N=47...")
1776 ok := iter.Next(&result)
1777 c.Assert(ok, Equals, true)
1778 c.Assert(iter.Err(), IsNil)
1779 c.Assert(iter.Timeout(), Equals, false)
1780 c.Assert(result.N, Equals, 47)
1781 c.Log("Got Next with N=47!")
1782
1783 // The following may break because it depends a bit on the internal
1784 // timing used by MongoDB's AwaitData logic. If it does, the problem
1785 // will be observed as more GET_MORE_OPs than predicted:
1786 // 1*QUERY_OP for nonce + 1*GET_MORE_OP on Next +
1787 // 1*INSERT_OP + 1*QUERY_OP for getLastError on insert of 47
1788 stats := mgo.GetStats()
1789 if s.versionAtLeast(3, 0) { // TODO Will be 2.6 when write commands are enabled for it.
1790 c.Assert(stats.SentOps, Equals, 3)
1791 } else {
1792 c.Assert(stats.SentOps, Equals, 4)
1793 }
1794 c.Assert(stats.ReceivedOps, Equals, 3) // REPLY_OPs for 1*QUERY_OP for nonce + 1*GET_MORE_OPs and 1*QUERY_OP
1795 c.Assert(stats.ReceivedDocs, Equals, 3) // nonce + N=47 result + getLastError response
1796
1797 c.Log("Will wait for a result which will never come...")
1798
1799 started := time.Now()
1800 ok = iter.Next(&result)
1801 c.Assert(ok, Equals, false)
1802 c.Assert(iter.Err(), IsNil)
1803 c.Assert(iter.Timeout(), Equals, true)
1804 c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
1805
1806 c.Log("Will now reuse the timed out tail cursor...")
1807
1808 coll.Insert(M{"n": 48})
1809 ok = iter.Next(&result)
1810 c.Assert(ok, Equals, true)
1811 c.Assert(iter.Close(), IsNil)
1812 c.Assert(iter.Timeout(), Equals, false)
1813 c.Assert(result.N, Equals, 48)
1814}
1815
1816// Test tailable cursors in a situation where Next never gets to sleep once
1817// to respect the timeout requested on Tail.
1818func (s *S) TestFindTailNoTimeout(c *C) {
1819 if *fast {
1820 c.Skip("-fast")
1821 }
1822
1823 session, err := mgo.Dial("localhost:40001")
1824 c.Assert(err, IsNil)
1825 defer session.Close()
1826
1827 cresult := struct{ ErrMsg string }{}
1828
1829 db := session.DB("mydb")
1830 err = db.Run(bson.D{{"create", "mycoll"}, {"capped", true}, {"size", 1024}}, &cresult)
1831 c.Assert(err, IsNil)
1832 c.Assert(cresult.ErrMsg, Equals, "")
1833 coll := db.C("mycoll")
1834
1835 ns := []int{40, 41, 42, 43, 44, 45, 46}
1836 for _, n := range ns {
1837 coll.Insert(M{"n": n})
1838 }
1839
1840 session.Refresh() // Release socket.
1841
1842 mgo.ResetStats()
1843
1844 query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
1845 iter := query.Tail(-1)
1846 c.Assert(err, IsNil)
1847
1848 n := len(ns)
1849 result := struct{ N int }{}
1850 for i := 2; i != n; i++ {
1851 ok := iter.Next(&result)
1852 c.Assert(ok, Equals, true)
1853 c.Assert(result.N, Equals, ns[i])
1854 if i == 3 { // The batch boundary.
1855 stats := mgo.GetStats()
1856 c.Assert(stats.ReceivedDocs, Equals, 2)
1857 }
1858 }
1859
1860 mgo.ResetStats()
1861
1862 // The following call to Next will block.
1863 go func() {
1864 time.Sleep(5e8)
1865 session := session.New()
1866 defer session.Close()
1867 coll := session.DB("mydb").C("mycoll")
1868 coll.Insert(M{"n": 47})
1869 }()
1870
1871 c.Log("Will wait for Next with N=47...")
1872 ok := iter.Next(&result)
1873 c.Assert(ok, Equals, true)
1874 c.Assert(iter.Err(), IsNil)
1875 c.Assert(iter.Timeout(), Equals, false)
1876 c.Assert(result.N, Equals, 47)
1877 c.Log("Got Next with N=47!")
1878
1879 // The following may break because it depends a bit on the internal
1880 // timing used by MongoDB's AwaitData logic. If it does, the problem
1881 // will be observed as more GET_MORE_OPs than predicted:
1882 // 1*QUERY_OP for nonce + 1*GET_MORE_OP on Next +
1883 // 1*INSERT_OP + 1*QUERY_OP for getLastError on insert of 47
1884 stats := mgo.GetStats()
1885 if s.versionAtLeast(3, 0) { // TODO Will be 2.6 when write commands are enabled for it.
1886 c.Assert(stats.SentOps, Equals, 3)
1887 } else {
1888 c.Assert(stats.SentOps, Equals, 4)
1889 }
1890 c.Assert(stats.ReceivedOps, Equals, 3) // REPLY_OPs for 1*QUERY_OP for nonce + 1*GET_MORE_OPs and 1*QUERY_OP
1891 c.Assert(stats.ReceivedDocs, Equals, 3) // nonce + N=47 result + getLastError response
1892
1893 c.Log("Will wait for a result which will never come...")
1894
1895 gotNext := make(chan bool)
1896 go func() {
1897 ok := iter.Next(&result)
1898 gotNext <- ok
1899 }()
1900
1901 select {
1902 case ok := <-gotNext:
1903 c.Fatalf("Next returned: %v", ok)
1904 case <-time.After(3e9):
1905 // Good. Should still be sleeping at that point.
1906 }
1907
1908 // Closing the session should cause Next to return.
1909 session.Close()
1910
1911 select {
1912 case ok := <-gotNext:
1913 c.Assert(ok, Equals, false)
1914 c.Assert(iter.Err(), ErrorMatches, "Closed explicitly")
1915 c.Assert(iter.Timeout(), Equals, false)
1916 case <-time.After(1e9):
1917 c.Fatal("Closing the session did not unblock Next")
1918 }
1919}
1920
1921func (s *S) TestIterNextResetsResult(c *C) {
1922 session, err := mgo.Dial("localhost:40001")
1923 c.Assert(err, IsNil)
1924 defer session.Close()
1925
1926 coll := session.DB("mydb").C("mycoll")
1927
1928 ns := []int{1, 2, 3}
1929 for _, n := range ns {
1930 coll.Insert(M{"n" + strconv.Itoa(n): n})
1931 }
1932
1933 query := coll.Find(nil).Sort("$natural")
1934
1935 i := 0
1936 var sresult *struct{ N1, N2, N3 int }
1937 iter := query.Iter()
1938 for iter.Next(&sresult) {
1939 switch i {
1940 case 0:
1941 c.Assert(sresult.N1, Equals, 1)
1942 c.Assert(sresult.N2+sresult.N3, Equals, 0)
1943 case 1:
1944 c.Assert(sresult.N2, Equals, 2)
1945 c.Assert(sresult.N1+sresult.N3, Equals, 0)
1946 case 2:
1947 c.Assert(sresult.N3, Equals, 3)
1948 c.Assert(sresult.N1+sresult.N2, Equals, 0)
1949 }
1950 i++
1951 }
1952 c.Assert(iter.Close(), IsNil)
1953
1954 i = 0
1955 var mresult M
1956 iter = query.Iter()
1957 for iter.Next(&mresult) {
1958 delete(mresult, "_id")
1959 switch i {
1960 case 0:
1961 c.Assert(mresult, DeepEquals, M{"n1": 1})
1962 case 1:
1963 c.Assert(mresult, DeepEquals, M{"n2": 2})
1964 case 2:
1965 c.Assert(mresult, DeepEquals, M{"n3": 3})
1966 }
1967 i++
1968 }
1969 c.Assert(iter.Close(), IsNil)
1970
1971 i = 0
1972 var iresult interface{}
1973 iter = query.Iter()
1974 for iter.Next(&iresult) {
1975 mresult, ok := iresult.(bson.M)
1976 c.Assert(ok, Equals, true, Commentf("%#v", iresult))
1977 delete(mresult, "_id")
1978 switch i {
1979 case 0:
1980 c.Assert(mresult, DeepEquals, bson.M{"n1": 1})
1981 case 1:
1982 c.Assert(mresult, DeepEquals, bson.M{"n2": 2})
1983 case 2:
1984 c.Assert(mresult, DeepEquals, bson.M{"n3": 3})
1985 }
1986 i++
1987 }
1988 c.Assert(iter.Close(), IsNil)
1989}
1990
1991func (s *S) TestFindForOnIter(c *C) {
1992 session, err := mgo.Dial("localhost:40001")
1993 c.Assert(err, IsNil)
1994 defer session.Close()
1995
1996 coll := session.DB("mydb").C("mycoll")
1997
1998 ns := []int{40, 41, 42, 43, 44, 45, 46}
1999 for _, n := range ns {
2000 coll.Insert(M{"n": n})
2001 }
2002
2003 session.Refresh() // Release socket.
2004
2005 mgo.ResetStats()
2006
2007 query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
2008 iter := query.Iter()
2009
2010 i := 2
2011 var result *struct{ N int }
2012 err = iter.For(&result, func() error {
2013 c.Assert(i < 7, Equals, true)
2014 c.Assert(result.N, Equals, ns[i])
2015 if i == 1 {
2016 stats := mgo.GetStats()
2017 c.Assert(stats.ReceivedDocs, Equals, 2)
2018 }
2019 i++
2020 return nil
2021 })
2022 c.Assert(err, IsNil)
2023
2024 session.Refresh() // Release socket.
2025
2026 stats := mgo.GetStats()
2027 c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
2028 c.Assert(stats.ReceivedOps, Equals, 3) // and their REPLY_OPs.
2029 c.Assert(stats.ReceivedDocs, Equals, 5)
2030 c.Assert(stats.SocketsInUse, Equals, 0)
2031}
2032
2033func (s *S) TestFindFor(c *C) {
2034 session, err := mgo.Dial("localhost:40001")
2035 c.Assert(err, IsNil)
2036 defer session.Close()
2037
2038 coll := session.DB("mydb").C("mycoll")
2039
2040 ns := []int{40, 41, 42, 43, 44, 45, 46}
2041 for _, n := range ns {
2042 coll.Insert(M{"n": n})
2043 }
2044
2045 session.Refresh() // Release socket.
2046
2047 mgo.ResetStats()
2048
2049 query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
2050
2051 i := 2
2052 var result *struct{ N int }
2053 err = query.For(&result, func() error {
2054 c.Assert(i < 7, Equals, true)
2055 c.Assert(result.N, Equals, ns[i])
2056 if i == 1 {
2057 stats := mgo.GetStats()
2058 c.Assert(stats.ReceivedDocs, Equals, 2)
2059 }
2060 i++
2061 return nil
2062 })
2063 c.Assert(err, IsNil)
2064
2065 session.Refresh() // Release socket.
2066
2067 stats := mgo.GetStats()
2068 c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
2069 c.Assert(stats.ReceivedOps, Equals, 3) // and their REPLY_OPs.
2070 c.Assert(stats.ReceivedDocs, Equals, 5)
2071 c.Assert(stats.SocketsInUse, Equals, 0)
2072}
2073
2074func (s *S) TestFindForStopOnError(c *C) {
2075 session, err := mgo.Dial("localhost:40001")
2076 c.Assert(err, IsNil)
2077 defer session.Close()
2078
2079 coll := session.DB("mydb").C("mycoll")
2080
2081 ns := []int{40, 41, 42, 43, 44, 45, 46}
2082 for _, n := range ns {
2083 coll.Insert(M{"n": n})
2084 }
2085
2086 query := coll.Find(M{"n": M{"$gte": 42}})
2087 i := 2
2088 var result *struct{ N int }
2089 err = query.For(&result, func() error {
2090 c.Assert(i < 4, Equals, true)
2091 c.Assert(result.N, Equals, ns[i])
2092 if i == 3 {
2093 return fmt.Errorf("stop!")
2094 }
2095 i++
2096 return nil
2097 })
2098 c.Assert(err, ErrorMatches, "stop!")
2099}
2100
2101func (s *S) TestFindForResetsResult(c *C) {
2102 session, err := mgo.Dial("localhost:40001")
2103 c.Assert(err, IsNil)
2104 defer session.Close()
2105
2106 coll := session.DB("mydb").C("mycoll")
2107
2108 ns := []int{1, 2, 3}
2109 for _, n := range ns {
2110 coll.Insert(M{"n" + strconv.Itoa(n): n})
2111 }
2112
2113 query := coll.Find(nil).Sort("$natural")
2114
2115 i := 0
2116 var sresult *struct{ N1, N2, N3 int }
2117 err = query.For(&sresult, func() error {
2118 switch i {
2119 case 0:
2120 c.Assert(sresult.N1, Equals, 1)
2121 c.Assert(sresult.N2+sresult.N3, Equals, 0)
2122 case 1:
2123 c.Assert(sresult.N2, Equals, 2)
2124 c.Assert(sresult.N1+sresult.N3, Equals, 0)
2125 case 2:
2126 c.Assert(sresult.N3, Equals, 3)
2127 c.Assert(sresult.N1+sresult.N2, Equals, 0)
2128 }
2129 i++
2130 return nil
2131 })
2132 c.Assert(err, IsNil)
2133
2134 i = 0
2135 var mresult M
2136 err = query.For(&mresult, func() error {
2137 delete(mresult, "_id")
2138 switch i {
2139 case 0:
2140 c.Assert(mresult, DeepEquals, M{"n1": 1})
2141 case 1:
2142 c.Assert(mresult, DeepEquals, M{"n2": 2})
2143 case 2:
2144 c.Assert(mresult, DeepEquals, M{"n3": 3})
2145 }
2146 i++
2147 return nil
2148 })
2149 c.Assert(err, IsNil)
2150
2151 i = 0
2152 var iresult interface{}
2153 err = query.For(&iresult, func() error {
2154 mresult, ok := iresult.(bson.M)
2155 c.Assert(ok, Equals, true, Commentf("%#v", iresult))
2156 delete(mresult, "_id")
2157 switch i {
2158 case 0:
2159 c.Assert(mresult, DeepEquals, bson.M{"n1": 1})
2160 case 1:
2161 c.Assert(mresult, DeepEquals, bson.M{"n2": 2})
2162 case 2:
2163 c.Assert(mresult, DeepEquals, bson.M{"n3": 3})
2164 }
2165 i++
2166 return nil
2167 })
2168 c.Assert(err, IsNil)
2169}
2170
2171func (s *S) TestFindIterSnapshot(c *C) {
2172 session, err := mgo.Dial("localhost:40001")
2173 c.Assert(err, IsNil)
2174 defer session.Close()
2175
2176 // Insane amounts of logging otherwise due to the
2177 // amount of data being shuffled.
2178 mgo.SetDebug(false)
2179 defer mgo.SetDebug(true)
2180
2181 coll := session.DB("mydb").C("mycoll")
2182
2183 var a [1024000]byte
2184
2185 for n := 0; n < 10; n++ {
2186 err := coll.Insert(M{"_id": n, "n": n, "a1": &a})
2187 c.Assert(err, IsNil)
2188 }
2189
2190 query := coll.Find(M{"n": M{"$gt": -1}}).Batch(2).Prefetch(0)
2191 query.Snapshot()
2192 iter := query.Iter()
2193
2194 seen := map[int]bool{}
2195 result := struct {
2196 Id int "_id"
2197 }{}
2198 for iter.Next(&result) {
2199 if len(seen) == 2 {
2200 // Grow all entries so that they have to move.
2201 // Backwards so that the order is inverted.
2202 for n := 10; n >= 0; n-- {
2203 _, err := coll.Upsert(M{"_id": n}, M{"$set": M{"a2": &a}})
2204 c.Assert(err, IsNil)
2205 }
2206 }
2207 if seen[result.Id] {
2208 c.Fatalf("seen duplicated key: %d", result.Id)
2209 }
2210 seen[result.Id] = true
2211 }
2212 c.Assert(iter.Close(), IsNil)
2213}
2214
2215func (s *S) TestSort(c *C) {
2216 session, err := mgo.Dial("localhost:40001")
2217 c.Assert(err, IsNil)
2218 defer session.Close()
2219
2220 coll := session.DB("mydb").C("mycoll")
2221
2222 coll.Insert(M{"a": 1, "b": 1})
2223 coll.Insert(M{"a": 2, "b": 2})
2224 coll.Insert(M{"a": 2, "b": 1})
2225 coll.Insert(M{"a": 0, "b": 1})
2226 coll.Insert(M{"a": 2, "b": 0})
2227 coll.Insert(M{"a": 0, "b": 2})
2228 coll.Insert(M{"a": 1, "b": 2})
2229 coll.Insert(M{"a": 0, "b": 0})
2230 coll.Insert(M{"a": 1, "b": 0})
2231
2232 query := coll.Find(M{})
2233 query.Sort("-a") // Should be ignored.
2234 query.Sort("-b", "a")
2235 iter := query.Iter()
2236
2237 l := make([]int, 18)
2238 r := struct{ A, B int }{}
2239 for i := 0; i != len(l); i += 2 {
2240 ok := iter.Next(&r)
2241 c.Assert(ok, Equals, true)
2242 c.Assert(err, IsNil)
2243 l[i] = r.A
2244 l[i+1] = r.B
2245 }
2246
2247 c.Assert(l, DeepEquals, []int{0, 2, 1, 2, 2, 2, 0, 1, 1, 1, 2, 1, 0, 0, 1, 0, 2, 0})
2248}
2249
2250func (s *S) TestSortWithBadArgs(c *C) {
2251 session, err := mgo.Dial("localhost:40001")
2252 c.Assert(err, IsNil)
2253 defer session.Close()
2254
2255 coll := session.DB("mydb").C("mycoll")
2256
2257 f1 := func() { coll.Find(nil).Sort("") }
2258 f2 := func() { coll.Find(nil).Sort("+") }
2259 f3 := func() { coll.Find(nil).Sort("foo", "-") }
2260
2261 for _, f := range []func(){f1, f2, f3} {
2262 c.Assert(f, PanicMatches, "Sort: empty field name")
2263 }
2264}
2265
2266func (s *S) TestSortScoreText(c *C) {
2267 session, err := mgo.Dial("localhost:40001")
2268 c.Assert(err, IsNil)
2269 defer session.Close()
2270
2271 coll := session.DB("mydb").C("mycoll")
2272
2273 err = coll.EnsureIndex(mgo.Index{
2274 Key: []string{"$text:a", "$text:b"},
2275 })
2276 c.Assert(err, IsNil)
2277
2278 err = coll.Insert(M{
2279 "a": "none",
2280 "b": "twice: foo foo",
2281 })
2282 c.Assert(err, IsNil)
2283 err = coll.Insert(M{
2284 "a": "just once: foo",
2285 "b": "none",
2286 })
2287 c.Assert(err, IsNil)
2288 err = coll.Insert(M{
2289 "a": "many: foo foo foo",
2290 "b": "none",
2291 })
2292 c.Assert(err, IsNil)
2293 err = coll.Insert(M{
2294 "a": "none",
2295 "b": "none",
2296 "c": "ignore: foo",
2297 })
2298 c.Assert(err, IsNil)
2299
2300 query := coll.Find(M{"$text": M{"$search": "foo"}})
2301 query.Select(M{"score": M{"$meta": "textScore"}})
2302 query.Sort("$textScore:score")
2303 iter := query.Iter()
2304
2305 var r struct{ A, B string }
2306 var results []string
2307 for iter.Next(&r) {
2308 results = append(results, r.A, r.B)
2309 }
2310
2311 c.Assert(results, DeepEquals, []string{
2312 "many: foo foo foo", "none",
2313 "none", "twice: foo foo",
2314 "just once: foo", "none",
2315 })
2316}
2317
2318func (s *S) TestPrefetching(c *C) {
2319 session, err := mgo.Dial("localhost:40001")
2320 c.Assert(err, IsNil)
2321 defer session.Close()
2322
2323 coll := session.DB("mydb").C("mycoll")
2324
2325 const total = 600
2326 mgo.SetDebug(false)
2327 docs := make([]interface{}, total)
2328 for i := 0; i != total; i++ {
2329 docs[i] = bson.D{{"n", i}}
2330 }
2331 err = coll.Insert(docs...)
2332 c.Assert(err, IsNil)
2333
2334 for testi := 0; testi < 5; testi++ {
2335 mgo.ResetStats()
2336
2337 var iter *mgo.Iter
2338 var beforeMore int
2339
2340 switch testi {
2341 case 0: // The default session value.
2342 session.SetBatch(100)
2343 iter = coll.Find(M{}).Iter()
2344 beforeMore = 75
2345
2346 case 2: // Changing the session value.
2347 session.SetBatch(100)
2348 session.SetPrefetch(0.27)
2349 iter = coll.Find(M{}).Iter()
2350 beforeMore = 73
2351
2352 case 1: // Changing via query methods.
2353 iter = coll.Find(M{}).Prefetch(0.27).Batch(100).Iter()
2354 beforeMore = 73
2355
2356 case 3: // With prefetch on first document.
2357 iter = coll.Find(M{}).Prefetch(1.0).Batch(100).Iter()
2358 beforeMore = 0
2359
2360 case 4: // Without prefetch.
2361 iter = coll.Find(M{}).Prefetch(0).Batch(100).Iter()
2362 beforeMore = 100
2363 }
2364
2365 pings := 0
2366 for batchi := 0; batchi < len(docs)/100-1; batchi++ {
2367 c.Logf("Iterating over %d documents on batch %d", beforeMore, batchi)
2368 var result struct{ N int }
2369 for i := 0; i < beforeMore; i++ {
2370 ok := iter.Next(&result)
2371 c.Assert(ok, Equals, true, Commentf("iter.Err: %v", iter.Err()))
2372 }
2373 beforeMore = 99
2374 c.Logf("Done iterating.")
2375
2376 session.Run("ping", nil) // Roundtrip to settle down.
2377 pings++
2378
2379 stats := mgo.GetStats()
2380 c.Assert(stats.ReceivedDocs, Equals, (batchi+1)*100+pings)
2381
2382 c.Logf("Iterating over one more document on batch %d", batchi)
2383 ok := iter.Next(&result)
2384 c.Assert(ok, Equals, true, Commentf("iter.Err: %v", iter.Err()))
2385 c.Logf("Done iterating.")
2386
2387 session.Run("ping", nil) // Roundtrip to settle down.
2388 pings++
2389
2390 stats = mgo.GetStats()
2391 c.Assert(stats.ReceivedDocs, Equals, (batchi+2)*100+pings)
2392 }
2393 }
2394}
2395
2396func (s *S) TestSafeSetting(c *C) {
2397 session, err := mgo.Dial("localhost:40001")
2398 c.Assert(err, IsNil)
2399 defer session.Close()
2400
2401 // Check the default
2402 safe := session.Safe()
2403 c.Assert(safe.W, Equals, 0)
2404 c.Assert(safe.WMode, Equals, "")
2405 c.Assert(safe.WTimeout, Equals, 0)
2406 c.Assert(safe.FSync, Equals, false)
2407 c.Assert(safe.J, Equals, false)
2408
2409 // Tweak it
2410 session.SetSafe(&mgo.Safe{W: 1, WTimeout: 2, FSync: true})
2411 safe = session.Safe()
2412 c.Assert(safe.W, Equals, 1)
2413 c.Assert(safe.WMode, Equals, "")
2414 c.Assert(safe.WTimeout, Equals, 2)
2415 c.Assert(safe.FSync, Equals, true)
2416 c.Assert(safe.J, Equals, false)
2417
2418 // Reset it again.
2419 session.SetSafe(&mgo.Safe{})
2420 safe = session.Safe()
2421 c.Assert(safe.W, Equals, 0)
2422 c.Assert(safe.WMode, Equals, "")
2423 c.Assert(safe.WTimeout, Equals, 0)
2424 c.Assert(safe.FSync, Equals, false)
2425 c.Assert(safe.J, Equals, false)
2426
2427 // Ensure safety to something more conservative.
2428 session.SetSafe(&mgo.Safe{W: 5, WTimeout: 6, J: true})
2429 safe = session.Safe()
2430 c.Assert(safe.W, Equals, 5)
2431 c.Assert(safe.WMode, Equals, "")
2432 c.Assert(safe.WTimeout, Equals, 6)
2433 c.Assert(safe.FSync, Equals, false)
2434 c.Assert(safe.J, Equals, true)
2435
2436 // Ensure safety to something less conservative won't change it.
2437 session.EnsureSafe(&mgo.Safe{W: 4, WTimeout: 7})
2438 safe = session.Safe()
2439 c.Assert(safe.W, Equals, 5)
2440 c.Assert(safe.WMode, Equals, "")
2441 c.Assert(safe.WTimeout, Equals, 6)
2442 c.Assert(safe.FSync, Equals, false)
2443 c.Assert(safe.J, Equals, true)
2444
2445 // But to something more conservative will.
2446 session.EnsureSafe(&mgo.Safe{W: 6, WTimeout: 4, FSync: true})
2447 safe = session.Safe()
2448 c.Assert(safe.W, Equals, 6)
2449 c.Assert(safe.WMode, Equals, "")
2450 c.Assert(safe.WTimeout, Equals, 4)
2451 c.Assert(safe.FSync, Equals, true)
2452 c.Assert(safe.J, Equals, false)
2453
2454 // Even more conservative.
2455 session.EnsureSafe(&mgo.Safe{WMode: "majority", WTimeout: 2})
2456 safe = session.Safe()
2457 c.Assert(safe.W, Equals, 0)
2458 c.Assert(safe.WMode, Equals, "majority")
2459 c.Assert(safe.WTimeout, Equals, 2)
2460 c.Assert(safe.FSync, Equals, true)
2461 c.Assert(safe.J, Equals, false)
2462
2463 // WMode always overrides, whatever it is, but J doesn't.
2464 session.EnsureSafe(&mgo.Safe{WMode: "something", J: true})
2465 safe = session.Safe()
2466 c.Assert(safe.W, Equals, 0)
2467 c.Assert(safe.WMode, Equals, "something")
2468 c.Assert(safe.WTimeout, Equals, 2)
2469 c.Assert(safe.FSync, Equals, true)
2470 c.Assert(safe.J, Equals, false)
2471
2472 // EnsureSafe with nil does nothing.
2473 session.EnsureSafe(nil)
2474 safe = session.Safe()
2475 c.Assert(safe.W, Equals, 0)
2476 c.Assert(safe.WMode, Equals, "something")
2477 c.Assert(safe.WTimeout, Equals, 2)
2478 c.Assert(safe.FSync, Equals, true)
2479 c.Assert(safe.J, Equals, false)
2480
2481 // Changing the safety of a cloned session doesn't touch the original.
2482 clone := session.Clone()
2483 defer clone.Close()
2484 clone.EnsureSafe(&mgo.Safe{WMode: "foo"})
2485 safe = session.Safe()
2486 c.Assert(safe.WMode, Equals, "something")
2487}
2488
2489func (s *S) TestSafeInsert(c *C) {
2490 session, err := mgo.Dial("localhost:40001")
2491 c.Assert(err, IsNil)
2492 defer session.Close()
2493
2494 coll := session.DB("mydb").C("mycoll")
2495
2496 // Insert an element with a predefined key.
2497 err = coll.Insert(M{"_id": 1})
2498 c.Assert(err, IsNil)
2499
2500 mgo.ResetStats()
2501
2502 // Session should be safe by default, so inserting it again must fail.
2503 err = coll.Insert(M{"_id": 1})
2504 c.Assert(err, ErrorMatches, ".*E11000 duplicate.*")
2505 c.Assert(err.(*mgo.LastError).Code, Equals, 11000)
2506
2507 // It must have sent two operations (INSERT_OP + getLastError QUERY_OP)
2508 stats := mgo.GetStats()
2509
2510 // TODO Will be 2.6 when write commands are enabled for it.
2511 if s.versionAtLeast(3, 0) {
2512 c.Assert(stats.SentOps, Equals, 1)
2513 } else {
2514 c.Assert(stats.SentOps, Equals, 2)
2515 }
2516
2517 mgo.ResetStats()
2518
2519 // If we disable safety, though, it won't complain.
2520 session.SetSafe(nil)
2521 err = coll.Insert(M{"_id": 1})
2522 c.Assert(err, IsNil)
2523
2524 // Must have sent a single operation this time (just the INSERT_OP)
2525 stats = mgo.GetStats()
2526 c.Assert(stats.SentOps, Equals, 1)
2527}
2528
2529func (s *S) TestSafeParameters(c *C) {
2530 session, err := mgo.Dial("localhost:40011")
2531 c.Assert(err, IsNil)
2532 defer session.Close()
2533
2534 coll := session.DB("mydb").C("mycoll")
2535
2536 // Tweak the safety parameters to something unachievable.
2537 session.SetSafe(&mgo.Safe{W: 4, WTimeout: 100})
2538 err = coll.Insert(M{"_id": 1})
2539 c.Assert(err, ErrorMatches, "timeout|timed out waiting for slaves|Not enough data-bearing nodes|waiting for replication timed out") // :-(
2540 if !s.versionAtLeast(2, 6) {
2541 // 2.6 turned it into a query error.
2542 c.Assert(err.(*mgo.LastError).WTimeout, Equals, true)
2543 }
2544}
2545
2546func (s *S) TestQueryErrorOne(c *C) {
2547 session, err := mgo.Dial("localhost:40001")
2548 c.Assert(err, IsNil)
2549 defer session.Close()
2550
2551 coll := session.DB("mydb").C("mycoll")
2552
2553 result := struct {
2554 Err string "$err"
2555 }{}
2556
2557 err = coll.Find(M{"a": 1}).Select(M{"a": M{"b": 1}}).One(&result)
2558 c.Assert(err, ErrorMatches, ".*Unsupported projection option:.*")
2559 c.Assert(err.(*mgo.QueryError).Message, Matches, ".*Unsupported projection option:.*")
2560 if s.versionAtLeast(2, 6) {
2561 // Oh, the dance of error codes. :-(
2562 c.Assert(err.(*mgo.QueryError).Code, Equals, 17287)
2563 } else {
2564 c.Assert(err.(*mgo.QueryError).Code, Equals, 13097)
2565 }
2566
2567 // The result should be properly unmarshalled with QueryError
2568 c.Assert(result.Err, Matches, ".*Unsupported projection option:.*")
2569}
2570
2571func (s *S) TestQueryErrorNext(c *C) {
2572 session, err := mgo.Dial("localhost:40001")
2573 c.Assert(err, IsNil)
2574 defer session.Close()
2575
2576 coll := session.DB("mydb").C("mycoll")
2577
2578 result := struct {
2579 Err string "$err"
2580 }{}
2581
2582 iter := coll.Find(M{"a": 1}).Select(M{"a": M{"b": 1}}).Iter()
2583
2584 ok := iter.Next(&result)
2585 c.Assert(ok, Equals, false)
2586
2587 err = iter.Close()
2588 c.Assert(err, ErrorMatches, ".*Unsupported projection option:.*")
2589 c.Assert(err.(*mgo.QueryError).Message, Matches, ".*Unsupported projection option:.*")
2590 if s.versionAtLeast(2, 6) {
2591 // Oh, the dance of error codes. :-(
2592 c.Assert(err.(*mgo.QueryError).Code, Equals, 17287)
2593 } else {
2594 c.Assert(err.(*mgo.QueryError).Code, Equals, 13097)
2595 }
2596 c.Assert(iter.Err(), Equals, err)
2597
2598 // The result should be properly unmarshalled with QueryError
2599 c.Assert(result.Err, Matches, ".*Unsupported projection option:.*")
2600}
2601
2602var indexTests = []struct {
2603 index mgo.Index
2604 expected M
2605}{{
2606 mgo.Index{
2607 Key: []string{"a"},
2608 Background: true,
2609 },
2610 M{
2611 "name": "a_1",
2612 "key": M{"a": 1},
2613 "ns": "mydb.mycoll",
2614 "background": true,
2615 },
2616}, {
2617 mgo.Index{
2618 Key: []string{"a", "-b"},
2619 Unique: true,
2620 DropDups: true,
2621 },
2622 M{
2623 "name": "a_1_b_-1",
2624 "key": M{"a": 1, "b": -1},
2625 "ns": "mydb.mycoll",
2626 "unique": true,
2627 "dropDups": true,
2628 },
2629}, {
2630 mgo.Index{
2631 Key: []string{"@loc_old"}, // Obsolete
2632 Min: -500,
2633 Max: 500,
2634 Bits: 32,
2635 },
2636 M{
2637 "name": "loc_old_2d",
2638 "key": M{"loc_old": "2d"},
2639 "ns": "mydb.mycoll",
2640 "min": -500,
2641 "max": 500,
2642 "bits": 32,
2643 },
2644}, {
2645 mgo.Index{
2646 Key: []string{"$2d:loc"},
2647 Min: -500,
2648 Max: 500,
2649 Bits: 32,
2650 },
2651 M{
2652 "name": "loc_2d",
2653 "key": M{"loc": "2d"},
2654 "ns": "mydb.mycoll",
2655 "min": -500,
2656 "max": 500,
2657 "bits": 32,
2658 },
2659}, {
2660 mgo.Index{
2661 Key: []string{"$geoHaystack:loc", "type"},
2662 BucketSize: 1,
2663 },
2664 M{
2665 "name": "loc_geoHaystack_type_1",
2666 "key": M{"loc": "geoHaystack", "type": 1},
2667 "ns": "mydb.mycoll",
2668 "bucketSize": 1.0,
2669 },
2670}, {
2671 mgo.Index{
2672 Key: []string{"$text:a", "$text:b"},
2673 Weights: map[string]int{"b": 42},
2674 },
2675 M{
2676 "name": "a_text_b_text",
2677 "key": M{"_fts": "text", "_ftsx": 1},
2678 "ns": "mydb.mycoll",
2679 "weights": M{"a": 1, "b": 42},
2680 "default_language": "english",
2681 "language_override": "language",
2682 "textIndexVersion": 2,
2683 },
2684}, {
2685 mgo.Index{
2686 Key: []string{"$text:a"},
2687 DefaultLanguage: "portuguese",
2688 LanguageOverride: "idioma",
2689 },
2690 M{
2691 "name": "a_text",
2692 "key": M{"_fts": "text", "_ftsx": 1},
2693 "ns": "mydb.mycoll",
2694 "weights": M{"a": 1},
2695 "default_language": "portuguese",
2696 "language_override": "idioma",
2697 "textIndexVersion": 2,
2698 },
2699}, {
2700 mgo.Index{
2701 Key: []string{"$text:$**"},
2702 },
2703 M{
2704 "name": "$**_text",
2705 "key": M{"_fts": "text", "_ftsx": 1},
2706 "ns": "mydb.mycoll",
2707 "weights": M{"$**": 1},
2708 "default_language": "english",
2709 "language_override": "language",
2710 "textIndexVersion": 2,
2711 },
2712}}
2713
2714func (s *S) TestEnsureIndex(c *C) {
2715 session, err := mgo.Dial("localhost:40001")
2716 c.Assert(err, IsNil)
2717 defer session.Close()
2718
2719 coll := session.DB("mydb").C("mycoll")
2720 idxs := session.DB("mydb").C("system.indexes")
2721
2722 for _, test := range indexTests {
2723 if !s.versionAtLeast(2, 4) && test.expected["weights"] != nil {
2724 // No text indexes until 2.4.
2725 continue
2726 }
2727
2728 err = coll.EnsureIndex(test.index)
2729 c.Assert(err, IsNil)
2730
2731 obtained := M{}
2732 err = idxs.Find(M{"name": test.expected["name"]}).One(obtained)
2733 c.Assert(err, IsNil)
2734
2735 delete(obtained, "v")
2736
2737 if s.versionAtLeast(2, 7) {
2738 // Was deprecated in 2.6, and not being reported by 2.7+.
2739 delete(test.expected, "dropDups")
2740 }
2741
2742 c.Assert(obtained, DeepEquals, test.expected)
2743
2744 err = coll.DropIndex(test.index.Key...)
2745 c.Assert(err, IsNil)
2746 }
2747}
2748
2749func (s *S) TestEnsureIndexWithBadInfo(c *C) {
2750 session, err := mgo.Dial("localhost:40001")
2751 c.Assert(err, IsNil)
2752 defer session.Close()
2753
2754 coll := session.DB("mydb").C("mycoll")
2755
2756 err = coll.EnsureIndex(mgo.Index{})
2757 c.Assert(err, ErrorMatches, "invalid index key:.*")
2758
2759 err = coll.EnsureIndex(mgo.Index{Key: []string{""}})
2760 c.Assert(err, ErrorMatches, "invalid index key:.*")
2761}
2762
2763func (s *S) TestEnsureIndexWithUnsafeSession(c *C) {
2764 session, err := mgo.Dial("localhost:40001")
2765 c.Assert(err, IsNil)
2766 defer session.Close()
2767
2768 session.SetSafe(nil)
2769
2770 coll := session.DB("mydb").C("mycoll")
2771
2772 err = coll.Insert(M{"a": 1})
2773 c.Assert(err, IsNil)
2774
2775 err = coll.Insert(M{"a": 1})
2776 c.Assert(err, IsNil)
2777
2778 // Should fail since there are duplicated entries.
2779 index := mgo.Index{
2780 Key: []string{"a"},
2781 Unique: true,
2782 }
2783
2784 err = coll.EnsureIndex(index)
2785 c.Assert(err, ErrorMatches, ".*duplicate key error.*")
2786}
2787
2788func (s *S) TestEnsureIndexKey(c *C) {
2789 session, err := mgo.Dial("localhost:40001")
2790 c.Assert(err, IsNil)
2791 defer session.Close()
2792
2793 coll := session.DB("mydb").C("mycoll")
2794
2795 err = coll.EnsureIndexKey("a")
2796 c.Assert(err, IsNil)
2797
2798 err = coll.EnsureIndexKey("a", "-b")
2799 c.Assert(err, IsNil)
2800
2801 sysidx := session.DB("mydb").C("system.indexes")
2802
2803 result1 := M{}
2804 err = sysidx.Find(M{"name": "a_1"}).One(result1)
2805 c.Assert(err, IsNil)
2806
2807 result2 := M{}
2808 err = sysidx.Find(M{"name": "a_1_b_-1"}).One(result2)
2809 c.Assert(err, IsNil)
2810
2811 delete(result1, "v")
2812 expected1 := M{
2813 "name": "a_1",
2814 "key": M{"a": 1},
2815 "ns": "mydb.mycoll",
2816 }
2817 c.Assert(result1, DeepEquals, expected1)
2818
2819 delete(result2, "v")
2820 expected2 := M{
2821 "name": "a_1_b_-1",
2822 "key": M{"a": 1, "b": -1},
2823 "ns": "mydb.mycoll",
2824 }
2825 c.Assert(result2, DeepEquals, expected2)
2826}
2827
2828func (s *S) TestEnsureIndexDropIndex(c *C) {
2829 session, err := mgo.Dial("localhost:40001")
2830 c.Assert(err, IsNil)
2831 defer session.Close()
2832
2833 coll := session.DB("mydb").C("mycoll")
2834
2835 err = coll.EnsureIndexKey("a")
2836 c.Assert(err, IsNil)
2837
2838 err = coll.EnsureIndexKey("-b")
2839 c.Assert(err, IsNil)
2840
2841 err = coll.DropIndex("-b")
2842 c.Assert(err, IsNil)
2843
2844 sysidx := session.DB("mydb").C("system.indexes")
2845 dummy := &struct{}{}
2846
2847 err = sysidx.Find(M{"name": "a_1"}).One(dummy)
2848 c.Assert(err, IsNil)
2849
2850 err = sysidx.Find(M{"name": "b_1"}).One(dummy)
2851 c.Assert(err, Equals, mgo.ErrNotFound)
2852
2853 err = coll.DropIndex("a")
2854 c.Assert(err, IsNil)
2855
2856 err = sysidx.Find(M{"name": "a_1"}).One(dummy)
2857 c.Assert(err, Equals, mgo.ErrNotFound)
2858
2859 err = coll.DropIndex("a")
2860 c.Assert(err, ErrorMatches, "index not found.*")
2861}
2862
2863func (s *S) TestEnsureIndexCaching(c *C) {
2864 session, err := mgo.Dial("localhost:40001")
2865 c.Assert(err, IsNil)
2866 defer session.Close()
2867
2868 coll := session.DB("mydb").C("mycoll")
2869
2870 err = coll.EnsureIndexKey("a")
2871 c.Assert(err, IsNil)
2872
2873 mgo.ResetStats()
2874
2875 // Second EnsureIndex should be cached and do nothing.
2876 err = coll.EnsureIndexKey("a")
2877 c.Assert(err, IsNil)
2878
2879 stats := mgo.GetStats()
2880 c.Assert(stats.SentOps, Equals, 0)
2881
2882 // Resetting the cache should make it contact the server again.
2883 session.ResetIndexCache()
2884
2885 err = coll.EnsureIndexKey("a")
2886 c.Assert(err, IsNil)
2887
2888 stats = mgo.GetStats()
2889 c.Assert(stats.SentOps > 0, Equals, true)
2890
2891 // Dropping the index should also drop the cached index key.
2892 err = coll.DropIndex("a")
2893 c.Assert(err, IsNil)
2894
2895 mgo.ResetStats()
2896
2897 err = coll.EnsureIndexKey("a")
2898 c.Assert(err, IsNil)
2899
2900 stats = mgo.GetStats()
2901 c.Assert(stats.SentOps > 0, Equals, true)
2902}
2903
2904func (s *S) TestEnsureIndexGetIndexes(c *C) {
2905 session, err := mgo.Dial("localhost:40001")
2906 c.Assert(err, IsNil)
2907 defer session.Close()
2908
2909 coll := session.DB("mydb").C("mycoll")
2910
2911 err = coll.EnsureIndexKey("-b")
2912 c.Assert(err, IsNil)
2913
2914 err = coll.EnsureIndexKey("a")
2915 c.Assert(err, IsNil)
2916
2917 // Obsolete.
2918 err = coll.EnsureIndexKey("@c")
2919 c.Assert(err, IsNil)
2920
2921 err = coll.EnsureIndexKey("$2d:d")
2922 c.Assert(err, IsNil)
2923
2924 // Try to exercise cursor logic. 2.8.0-rc3 still ignores this.
2925 session.SetBatch(2)
2926
2927 indexes, err := coll.Indexes()
2928 c.Assert(err, IsNil)
2929
2930 c.Assert(indexes[0].Name, Equals, "_id_")
2931 c.Assert(indexes[1].Name, Equals, "a_1")
2932 c.Assert(indexes[1].Key, DeepEquals, []string{"a"})
2933 c.Assert(indexes[2].Name, Equals, "b_-1")
2934 c.Assert(indexes[2].Key, DeepEquals, []string{"-b"})
2935 c.Assert(indexes[3].Name, Equals, "c_2d")
2936 c.Assert(indexes[3].Key, DeepEquals, []string{"$2d:c"})
2937 c.Assert(indexes[4].Name, Equals, "d_2d")
2938 c.Assert(indexes[4].Key, DeepEquals, []string{"$2d:d"})
2939}
2940
2941func (s *S) TestEnsureIndexEvalGetIndexes(c *C) {
2942 session, err := mgo.Dial("localhost:40001")
2943 c.Assert(err, IsNil)
2944 defer session.Close()
2945
2946 coll := session.DB("mydb").C("mycoll")
2947
2948 err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({b: -1})"}}, nil)
2949 c.Assert(err, IsNil)
2950 err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({a: 1})"}}, nil)
2951 c.Assert(err, IsNil)
2952 err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({c: -1, e: 1})"}}, nil)
2953 c.Assert(err, IsNil)
2954 err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({d: '2d'})"}}, nil)
2955 c.Assert(err, IsNil)
2956
2957 indexes, err := coll.Indexes()
2958 c.Assert(err, IsNil)
2959
2960 c.Assert(indexes[0].Name, Equals, "_id_")
2961 c.Assert(indexes[1].Name, Equals, "a_1")
2962 c.Assert(indexes[1].Key, DeepEquals, []string{"a"})
2963 c.Assert(indexes[2].Name, Equals, "b_-1")
2964 c.Assert(indexes[2].Key, DeepEquals, []string{"-b"})
2965 c.Assert(indexes[3].Name, Equals, "c_-1_e_1")
2966 c.Assert(indexes[3].Key, DeepEquals, []string{"-c", "e"})
2967 if s.versionAtLeast(2, 2) {
2968 c.Assert(indexes[4].Name, Equals, "d_2d")
2969 c.Assert(indexes[4].Key, DeepEquals, []string{"$2d:d"})
2970 } else {
2971 c.Assert(indexes[4].Name, Equals, "d_")
2972 c.Assert(indexes[4].Key, DeepEquals, []string{"$2d:d"})
2973 }
2974}
2975
2976var testTTL = flag.Bool("test-ttl", false, "test TTL collections (may take 1 minute)")
2977
2978func (s *S) TestEnsureIndexExpireAfter(c *C) {
2979 session, err := mgo.Dial("localhost:40001")
2980 c.Assert(err, IsNil)
2981 defer session.Close()
2982
2983 session.SetSafe(nil)
2984
2985 coll := session.DB("mydb").C("mycoll")
2986
2987 err = coll.Insert(M{"n": 1, "t": time.Now().Add(-120 * time.Second)})
2988 c.Assert(err, IsNil)
2989 err = coll.Insert(M{"n": 2, "t": time.Now()})
2990 c.Assert(err, IsNil)
2991
2992 // Should fail since there are duplicated entries.
2993 index := mgo.Index{
2994 Key: []string{"t"},
2995 ExpireAfter: 1 * time.Minute,
2996 }
2997
2998 err = coll.EnsureIndex(index)
2999 c.Assert(err, IsNil)
3000
3001 indexes, err := coll.Indexes()
3002 c.Assert(err, IsNil)
3003 c.Assert(indexes[1].Name, Equals, "t_1")
3004 c.Assert(indexes[1].ExpireAfter, Equals, 1*time.Minute)
3005
3006 if *testTTL {
3007 worked := false
3008 stop := time.Now().Add(70 * time.Second)
3009 for time.Now().Before(stop) {
3010 n, err := coll.Count()
3011 c.Assert(err, IsNil)
3012 if n == 1 {
3013 worked = true
3014 break
3015 }
3016 c.Assert(n, Equals, 2)
3017 c.Logf("Still has 2 entries...")
3018 time.Sleep(1 * time.Second)
3019 }
3020 if !worked {
3021 c.Fatalf("TTL index didn't work")
3022 }
3023 }
3024}
3025
3026func (s *S) TestDistinct(c *C) {
3027 session, err := mgo.Dial("localhost:40001")
3028 c.Assert(err, IsNil)
3029 defer session.Close()
3030
3031 coll := session.DB("mydb").C("mycoll")
3032
3033 for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
3034 coll.Insert(M{"n": i})
3035 }
3036
3037 var result []int
3038 err = coll.Find(M{"n": M{"$gt": 2}}).Sort("n").Distinct("n", &result)
3039
3040 sort.IntSlice(result).Sort()
3041 c.Assert(result, DeepEquals, []int{3, 4, 6})
3042}
3043
3044func (s *S) TestMapReduce(c *C) {
3045 session, err := mgo.Dial("localhost:40001")
3046 c.Assert(err, IsNil)
3047 defer session.Close()
3048
3049 coll := session.DB("mydb").C("mycoll")
3050
3051 for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
3052 coll.Insert(M{"n": i})
3053 }
3054
3055 job := &mgo.MapReduce{
3056 Map: "function() { emit(this.n, 1); }",
3057 Reduce: "function(key, values) { return Array.sum(values); }",
3058 }
3059 var result []struct {
3060 Id int "_id"
3061 Value int
3062 }
3063
3064 info, err := coll.Find(M{"n": M{"$gt": 2}}).MapReduce(job, &result)
3065 c.Assert(err, IsNil)
3066 c.Assert(info.InputCount, Equals, 4)
3067 c.Assert(info.EmitCount, Equals, 4)
3068 c.Assert(info.OutputCount, Equals, 3)
3069 c.Assert(info.VerboseTime, IsNil)
3070
3071 expected := map[int]int{3: 1, 4: 2, 6: 1}
3072 for _, item := range result {
3073 c.Logf("Item: %#v", &item)
3074 c.Assert(item.Value, Equals, expected[item.Id])
3075 expected[item.Id] = -1
3076 }
3077}
3078
3079func (s *S) TestMapReduceFinalize(c *C) {
3080 session, err := mgo.Dial("localhost:40001")
3081 c.Assert(err, IsNil)
3082 defer session.Close()
3083
3084 coll := session.DB("mydb").C("mycoll")
3085
3086 for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
3087 coll.Insert(M{"n": i})
3088 }
3089
3090 job := &mgo.MapReduce{
3091 Map: "function() { emit(this.n, 1) }",
3092 Reduce: "function(key, values) { return Array.sum(values) }",
3093 Finalize: "function(key, count) { return {count: count} }",
3094 }
3095 var result []struct {
3096 Id int "_id"
3097 Value struct{ Count int }
3098 }
3099 _, err = coll.Find(nil).MapReduce(job, &result)
3100 c.Assert(err, IsNil)
3101
3102 expected := map[int]int{1: 1, 2: 2, 3: 1, 4: 2, 6: 1}
3103 for _, item := range result {
3104 c.Logf("Item: %#v", &item)
3105 c.Assert(item.Value.Count, Equals, expected[item.Id])
3106 expected[item.Id] = -1
3107 }
3108}
3109
3110func (s *S) TestMapReduceToCollection(c *C) {
3111 session, err := mgo.Dial("localhost:40001")
3112 c.Assert(err, IsNil)
3113 defer session.Close()

591 more lines aren’t shown here.

vendor/gopkg.in/mgo.v2/socket.go+677−0

This diff is large: its 678 lines aren’t shown here.

vendor/gopkg.in/mgo.v2/stats.go+147−0

This diff is large: its 148 lines aren’t shown here.

vendor/gopkg.in/mgo.v2/suite_test.go+254−0

This diff is large: its 255 lines aren’t shown here.

vendor/gopkg.in/mgo.v2/syscall_test.go+15−0

This diff is large: its 16 lines aren’t shown here.

vendor/gopkg.in/mgo.v2/syscall_windows_test.go+11−0

This diff is large: its 12 lines aren’t shown here.

version_test.go+5−1

This diff is large: its 13 lines aren’t shown here.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t