Unified · Split
| @@ -34,7 +34,8 @@ lint: · 33 unmodified lines | ||
| 34 | 34 | |
| 35 | 35 | cyclo: |
| 36 | 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 | |
| 39 | 40 | build: getdeps verifiers |
| 40 | 41 | @echo "Installing minio:" |
| @@ -42,6 +43,7 @@ build: getdeps verifiers · 1 unmodified line | ||
| 42 | 43 | |
| 43 | 44 | test: build |
| 44 | 45 | @echo "Running all testing:" |
| 46 | + @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) . | |
| 45 | 47 | @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg... |
| 46 | 48 | |
| 47 | 49 | gomake-all: build |
| @@ -21,7 +21,7 @@ _init() { · 20 unmodified lines | ||
| 21 | 21 | |
| 22 | 22 | ## Minimum required versions for build dependencies |
| 23 | 23 | GCC_VERSION="4.0" |
| 24 | − CLANG_VERSION="3.5" | |
| 24 | + LLVM_VERSION="7.0.0" | |
| 25 | 25 | YASM_VERSION="1.2.0" |
| 26 | 26 | GIT_VERSION="1.0" |
| 27 | 27 | GO_VERSION="1.5.1" |
| @@ -173,7 +173,7 @@ is_supported_arch() { · 145 unmodified lines | ||
| 173 | 173 | check_deps() { |
| 174 | 174 | check_version "$(env go version 2>/dev/null | sed 's/^.* go\([0-9.]*\).*$/\1/')" "${GO_VERSION}" |
| 175 | 175 | if [ $? -ge 2 ]; then |
| 176 | − MISSING="${MISSING} golang(1.5)" | |
| 176 | + MISSING="${MISSING} golang(${GO_VERSION})" | |
| 177 | 177 | fi |
| 178 | 178 | |
| 179 | 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 | 185 | "Linux") |
| 186 | 186 | check_version "$(env gcc --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${GCC_VERSION}" |
| 187 | 187 | if [ $? -ge 2 ]; then |
| 188 | − MISSING="${MISSING} build-essential" | |
| 188 | + MISSING="${MISSING} build-essential(${GCC_VERSION})" | |
| 189 | 189 | fi |
| 190 | 190 | ;; |
| 191 | 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 | 193 | if [ $? -ge 2 ]; then |
| 194 | − MISSING="${MISSING} xcode-cli" | |
| 194 | + MISSING="${MISSING} xcode-cli(${LLVM_VERSION})" | |
| 195 | 195 | fi |
| 196 | 196 | ;; |
| 197 | 197 | "*") |
| @@ -200,7 +200,7 @@ check_deps() { · 2 unmodified lines | ||
| 200 | 200 | |
| 201 | 201 | check_version "$(env yasm --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${YASM_VERSION}" |
| 202 | 202 | if [ $? -ge 2 ]; then |
| 203 | − MISSING="${MISSING} yasm(1.2.0)" | |
| 203 | + MISSING="${MISSING} yasm(${YASM_VERSION})" | |
| 204 | 204 | fi |
| 205 | 205 | |
| 206 | 206 | } |
| @@ -16,10 +16,7 @@ · 15 unmodified lines | ||
| 16 | 16 | |
| 17 | 17 | package main |
| 18 | 18 | |
| 19 | −import ( | |
| 20 | − "github.com/minio/cli" | |
| 21 | − "github.com/minio/minio/pkg/controller" | |
| 22 | −) | |
| 19 | +import "github.com/minio/cli" | |
| 23 | 20 | |
| 24 | 21 | var controllerCmd = cli.Command{ |
| 25 | 22 | Name: "controller", |
| @@ -43,6 +40,6 @@ func controllerMain(c *cli.Context) { · 17 unmodified lines | ||
| 43 | 40 | cli.ShowCommandHelpAndExit(c, "controller", 1) |
| 44 | 41 | } |
| 45 | 42 | |
| 46 | − err := controller.Start() | |
| 43 | + err := StartController() | |
| 47 | 44 | errorIf(err.Trace(), "Failed to start minio controller.", nil) |
| 48 | 45 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package controller | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| @@ -22,17 +22,16 @@ import ( · 1 unmodified line | ||
| 22 | 22 | router "github.com/gorilla/mux" |
| 23 | 23 | jsonrpc "github.com/gorilla/rpc/v2" |
| 24 | 24 | "github.com/gorilla/rpc/v2/json" |
| 25 | − "github.com/minio/minio/pkg/controller/rpc" | |
| 26 | 25 | ) |
| 27 | 26 | |
| 28 | 27 | // getRPCHandler rpc handler |
| 29 | 28 | func getRPCHandler() http.Handler { |
| 30 | 29 | s := jsonrpc.NewServer() |
| 31 | 30 | s.RegisterCodec(json.NewCodec(), "application/json") |
| 32 | − s.RegisterService(new(rpc.VersionService), "Version") | |
| 33 | − s.RegisterService(new(rpc.DonutService), "Donut") | |
| 34 | − s.RegisterService(new(rpc.AuthService), "Auth") | |
| 35 | − s.RegisterService(new(rpc.ServerService), "Server") | |
| 31 | + s.RegisterService(new(VersionService), "Version") | |
| 32 | + s.RegisterService(new(DonutService), "Donut") | |
| 33 | + s.RegisterService(new(AuthService), "Auth") | |
| 34 | + s.RegisterService(new(ServerService), "Server") | |
| 36 | 35 | // Add new RPC services here |
| 37 | 36 | return registerRPC(router.NewRouter(), s) |
| 38 | 37 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package rpc | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "errors" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package rpc | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package rpc | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package rpc | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| @@ -38,8 +38,7 @@ type VersionReply struct { · 17 unmodified lines | ||
| 38 | 38 | // Get version |
| 39 | 39 | func (v *VersionService) Get(r *http.Request, args *VersionArgs, reply *VersionReply) error { |
| 40 | 40 | reply.Version = "0.0.1" |
| 41 | − //TODO: Better approach needed here to pass global states like version. --ab. | |
| 42 | − // reply.BuildDate = version.Version | |
| 41 | + reply.BuildDate = minioVersion | |
| 43 | 42 | reply.Architecture = runtime.GOARCH |
| 44 | 43 | reply.OperatingSystem = runtime.GOOS |
| 45 | 44 | return nil |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package controller | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "fmt" |
| @@ -54,8 +54,8 @@ func getRPCServer(rpcHandler http.Handler) (*http.Server, *probe.Error) { · 33 unmodified lines | ||
| 54 | 54 | return httpServer, nil |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | −// Start starts a controller | |
| 58 | −func Start() *probe.Error { | |
| 57 | +// StartController starts a minio controller | |
| 58 | +func StartController() *probe.Error { | |
| 59 | 59 | rpcServer, err := getRPCServer(getRPCHandler()) |
| 60 | 60 | if err != nil { |
| 61 | 61 | return err.Trace() |
| @@ -14,31 +14,26 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package controller | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "io/ioutil" |
| 21 | 21 | "net/http" |
| 22 | 22 | "net/http/httptest" |
| 23 | 23 | "os" |
| 24 | − "testing" | |
| 25 | 24 | |
| 26 | − jsonrpc "github.com/gorilla/rpc/v2/json" | |
| 25 | + "github.com/gorilla/rpc/v2/json" | |
| 27 | 26 | "github.com/minio/minio/pkg/auth" |
| 28 | − "github.com/minio/minio/pkg/controller/rpc" | |
| 29 | 27 | . "gopkg.in/check.v1" |
| 30 | 28 | ) |
| 31 | 29 | |
| 32 | −// Hook up gocheck into the "go test" runner. | |
| 33 | −func Test(t *testing.T) { TestingT(t) } | |
| 30 | +type ControllerRPCSuite struct{} | |
| 34 | 31 | |
| 35 | −type MySuite struct{} | |
| 36 | − | |
| 37 | −var _ = Suite(&MySuite{}) | |
| 32 | +var _ = Suite(&ControllerRPCSuite{}) | |
| 38 | 33 | |
| 39 | 34 | var testRPCServer *httptest.Server |
| 40 | 35 | |
| 41 | −func (s *MySuite) SetUpSuite(c *C) { | |
| 36 | +func (s *ControllerRPCSuite) SetUpSuite(c *C) { | |
| 42 | 37 | root, err := ioutil.TempDir(os.TempDir(), "api-") |
| 43 | 38 | c.Assert(err, IsNil) |
| 44 | 39 | auth.SetAuthConfigPath(root) |
| @@ -46,136 +41,136 @@ func (s *MySuite) SetUpSuite(c *C) { · 1 unmodified line | ||
| 46 | 41 | testRPCServer = httptest.NewServer(getRPCHandler()) |
| 47 | 42 | } |
| 48 | 43 | |
| 49 | −func (s *MySuite) TearDownSuite(c *C) { | |
| 44 | +func (s *ControllerRPCSuite) TearDownSuite(c *C) { | |
| 50 | 45 | testRPCServer.Close() |
| 51 | 46 | } |
| 52 | 47 | |
| 53 | −func (s *MySuite) TestMemStats(c *C) { | |
| 54 | − op := rpc.Operation{ | |
| 48 | +func (s *ControllerRPCSuite) TestMemStats(c *C) { | |
| 49 | + op := rpcOperation{ | |
| 55 | 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 | 54 | c.Assert(err, IsNil) |
| 60 | 55 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 61 | 56 | resp, err := req.Do() |
| 62 | 57 | c.Assert(err, IsNil) |
| 63 | 58 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 64 | 59 | |
| 65 | − var reply rpc.MemStatsReply | |
| 66 | − c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 60 | + var reply MemStatsReply | |
| 61 | + c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 67 | 62 | resp.Body.Close() |
| 68 | − c.Assert(reply, Not(DeepEquals), rpc.MemStatsReply{}) | |
| 63 | + c.Assert(reply, Not(DeepEquals), MemStatsReply{}) | |
| 69 | 64 | } |
| 70 | 65 | |
| 71 | −func (s *MySuite) TestSysInfo(c *C) { | |
| 72 | − op := rpc.Operation{ | |
| 66 | +func (s *ControllerRPCSuite) TestSysInfo(c *C) { | |
| 67 | + op := rpcOperation{ | |
| 73 | 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 | 72 | c.Assert(err, IsNil) |
| 78 | 73 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 79 | 74 | resp, err := req.Do() |
| 80 | 75 | c.Assert(err, IsNil) |
| 81 | 76 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 82 | 77 | |
| 83 | − var reply rpc.SysInfoReply | |
| 84 | − c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 78 | + var reply SysInfoReply | |
| 79 | + c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 85 | 80 | resp.Body.Close() |
| 86 | − c.Assert(reply, Not(DeepEquals), rpc.SysInfoReply{}) | |
| 81 | + c.Assert(reply, Not(DeepEquals), SysInfoReply{}) | |
| 87 | 82 | } |
| 88 | 83 | |
| 89 | −func (s *MySuite) TestServerList(c *C) { | |
| 90 | − op := rpc.Operation{ | |
| 84 | +func (s *ControllerRPCSuite) TestServerList(c *C) { | |
| 85 | + op := rpcOperation{ | |
| 91 | 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 | 90 | c.Assert(err, IsNil) |
| 96 | 91 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 97 | 92 | resp, err := req.Do() |
| 98 | 93 | c.Assert(err, IsNil) |
| 99 | 94 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 100 | 95 | |
| 101 | − var reply rpc.ServerListReply | |
| 102 | − c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 96 | + var reply ServerListReply | |
| 97 | + c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 103 | 98 | resp.Body.Close() |
| 104 | − c.Assert(reply, Not(DeepEquals), rpc.ServerListReply{}) | |
| 99 | + c.Assert(reply, Not(DeepEquals), ServerListReply{}) | |
| 105 | 100 | } |
| 106 | 101 | |
| 107 | −func (s *MySuite) TestServerAdd(c *C) { | |
| 108 | − op := rpc.Operation{ | |
| 102 | +func (s *ControllerRPCSuite) TestServerAdd(c *C) { | |
| 103 | + op := rpcOperation{ | |
| 109 | 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 | 108 | c.Assert(err, IsNil) |
| 114 | 109 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 115 | 110 | resp, err := req.Do() |
| 116 | 111 | c.Assert(err, IsNil) |
| 117 | 112 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 118 | 113 | |
| 119 | − var reply rpc.ServerAddReply | |
| 120 | − c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 114 | + var reply ServerAddReply | |
| 115 | + c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 121 | 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 | |
| 125 | −func (s *MySuite) TestAuth(c *C) { | |
| 126 | − op := rpc.Operation{ | |
| 120 | +func (s *ControllerRPCSuite) TestAuth(c *C) { | |
| 121 | + op := rpcOperation{ | |
| 127 | 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 | 126 | c.Assert(err, IsNil) |
| 132 | 127 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 133 | 128 | resp, err := req.Do() |
| 134 | 129 | c.Assert(err, IsNil) |
| 135 | 130 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 136 | 131 | |
| 137 | − var reply rpc.AuthReply | |
| 138 | − c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 132 | + var reply AuthReply | |
| 133 | + c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) | |
| 139 | 134 | resp.Body.Close() |
| 140 | − c.Assert(reply, Not(DeepEquals), rpc.AuthReply{}) | |
| 135 | + c.Assert(reply, Not(DeepEquals), AuthReply{}) | |
| 141 | 136 | c.Assert(len(reply.AccessKeyID), Equals, 20) |
| 142 | 137 | c.Assert(len(reply.SecretAccessKey), Equals, 40) |
| 143 | 138 | c.Assert(len(reply.Name), Not(Equals), 0) |
| 144 | 139 | |
| 145 | − op = rpc.Operation{ | |
| 140 | + op = rpcOperation{ | |
| 146 | 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 | 145 | c.Assert(err, IsNil) |
| 151 | 146 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 152 | 147 | resp, err = req.Do() |
| 153 | 148 | c.Assert(err, IsNil) |
| 154 | 149 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 155 | 150 | |
| 156 | − var newReply rpc.AuthReply | |
| 157 | − c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &newReply), IsNil) | |
| 151 | + var newReply AuthReply | |
| 152 | + c.Assert(json.DecodeClientResponse(resp.Body, &newReply), IsNil) | |
| 158 | 153 | resp.Body.Close() |
| 159 | − c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{}) | |
| 154 | + c.Assert(newReply, Not(DeepEquals), AuthReply{}) | |
| 160 | 155 | c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID) |
| 161 | 156 | c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey) |
| 162 | 157 | c.Assert(len(reply.Name), Not(Equals), 0) |
| 163 | 158 | |
| 164 | − op = rpc.Operation{ | |
| 159 | + op = rpcOperation{ | |
| 165 | 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 | 164 | c.Assert(err, IsNil) |
| 170 | 165 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 171 | 166 | resp, err = req.Do() |
| 172 | 167 | c.Assert(err, IsNil) |
| 173 | 168 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 174 | 169 | |
| 175 | − var resetReply rpc.AuthReply | |
| 176 | − c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &resetReply), IsNil) | |
| 170 | + var resetReply AuthReply | |
| 171 | + c.Assert(json.DecodeClientResponse(resp.Body, &resetReply), IsNil) | |
| 177 | 172 | resp.Body.Close() |
| 178 | − c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{}) | |
| 173 | + c.Assert(newReply, Not(DeepEquals), AuthReply{}) | |
| 179 | 174 | c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID) |
| 180 | 175 | c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey) |
| 181 | 176 | c.Assert(len(reply.Name), Not(Equals), 0) |
| @@ -183,11 +178,11 @@ func (s *MySuite) TestAuth(c *C) { · 1 unmodified line | ||
| 183 | 178 | // these operations should fail |
| 184 | 179 | |
| 185 | 180 | /// generating access for existing user fails |
| 186 | − op = rpc.Operation{ | |
| 181 | + op = rpcOperation{ | |
| 187 | 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 | 186 | c.Assert(err, IsNil) |
| 192 | 187 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 193 | 188 | resp, err = req.Do() |
| @@ -195,11 +190,11 @@ func (s *MySuite) TestAuth(c *C) { · 1 unmodified line | ||
| 195 | 190 | c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) |
| 196 | 191 | |
| 197 | 192 | /// null user provided invalid |
| 198 | − op = rpc.Operation{ | |
| 193 | + op = rpcOperation{ | |
| 199 | 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 | 198 | c.Assert(err, IsNil) |
| 204 | 199 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 205 | 200 | resp, err = req.Do() |
| @@ -27,7 +27,11 @@ import ( · 26 unmodified lines | ||
| 27 | 27 | . "gopkg.in/check.v1" |
| 28 | 28 | ) |
| 29 | 29 | |
| 30 | −func (s *TestSuite) TestLogger(c *C) { | |
| 30 | +type LoggerSuite struct{} | |
| 31 | + | |
| 32 | +var _ = Suite(&LoggerSuite{}) | |
| 33 | + | |
| 34 | +func (s *LoggerSuite) TestLogger(c *C) { | |
| 31 | 35 | var buffer bytes.Buffer |
| 32 | 36 | var fields logrus.Fields |
| 33 | 37 | log.Out = &buffer |
| @@ -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 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | 5 | * you may not use this file except in compliance with the License. |
| @@ -22,14 +22,5 @@ import ( · 16 unmodified lines | ||
| 22 | 22 | . "gopkg.in/check.v1" |
| 23 | 23 | ) |
| 24 | 24 | |
| 25 | +// Hook up gocheck into the "go test" runner. | |
| 25 | 26 | func Test(t *testing.T) { TestingT(t) } |
| 26 | − | |
| 27 | −type TestSuite struct{} | |
| 28 | − | |
| 29 | −var _ = Suite(&TestSuite{}) | |
| 30 | − | |
| 31 | −func (s *TestSuite) SetUpSuite(c *C) { | |
| 32 | −} | |
| 33 | − | |
| 34 | −func (s *TestSuite) TearDownSuite(c *C) { | |
| 35 | −} | |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package rpc | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "bytes" |
| @@ -24,20 +24,20 @@ import ( · 3 unmodified lines | ||
| 24 | 24 | "github.com/minio/minio/pkg/probe" |
| 25 | 25 | ) |
| 26 | 26 | |
| 27 | −// Operation RPC operation | |
| 28 | −type Operation struct { | |
| 27 | +// rpcOperation RPC operation | |
| 28 | +type rpcOperation struct { | |
| 29 | 29 | Method string |
| 30 | 30 | Request interface{} |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | −// Request rpc client request | |
| 34 | −type Request struct { | |
| 33 | +// rpcRequest rpc client request | |
| 34 | +type rpcRequest struct { | |
| 35 | 35 | req *http.Request |
| 36 | 36 | transport http.RoundTripper |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | −// NewRequest initiate a new client RPC request | |
| 40 | −func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request, *probe.Error) { | |
| 39 | +// newRPCRequest initiate a new client RPC request | |
| 40 | +func newRPCRequest(url string, op rpcOperation, transport http.RoundTripper) (*rpcRequest, *probe.Error) { | |
| 41 | 41 | params, err := json.EncodeClientRequest(op.Method, op.Request) |
| 42 | 42 | if err != nil { |
| 43 | 43 | return nil, probe.NewError(err) |
| @@ -46,7 +46,7 @@ func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request · 2 unmodified lines | ||
| 46 | 46 | if err != nil { |
| 47 | 47 | return nil, probe.NewError(err) |
| 48 | 48 | } |
| 49 | − rpcReq := &Request{} | |
| 49 | + rpcReq := &rpcRequest{} | |
| 50 | 50 | rpcReq.req = req |
| 51 | 51 | rpcReq.req.Header.Set("Content-Type", "application/json") |
| 52 | 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 | 59 | // Do - make a http connection |
| 60 | −func (r Request) Do() (*http.Response, *probe.Error) { | |
| 60 | +func (r rpcRequest) Do() (*http.Response, *probe.Error) { | |
| 61 | 61 | resp, err := r.transport.RoundTrip(r.req) |
| 62 | 62 | if err != nil { |
| 63 | 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 | 71 | // Get - get value of requested header |
| 72 | −func (r Request) Get(key string) string { | |
| 72 | +func (r rpcRequest) Get(key string) string { | |
| 73 | 73 | return r.req.Header.Get(key) |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | // Set - set value of a header key |
| 77 | −func (r *Request) Set(key, value string) { | |
| 77 | +func (r *rpcRequest) Set(key, value string) { | |
| 78 | 78 | r.req.Header.Set(key, value) |
| 79 | 79 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import "net/http" |
| 20 | 20 | |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| @@ -24,7 +24,7 @@ import ( · 3 unmodified lines | ||
| 24 | 24 | "github.com/minio/minio/pkg/probe" |
| 25 | 25 | ) |
| 26 | 26 | |
| 27 | −func (api Minio) isValidOp(w http.ResponseWriter, req *http.Request, acceptsContentType contentType) bool { | |
| 27 | +func (api MinioAPI) isValidOp(w http.ResponseWriter, req *http.Request, acceptsContentType contentType) bool { | |
| 28 | 28 | vars := mux.Vars(req) |
| 29 | 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 | 67 | // using the Initiate Multipart Upload request, but has not yet been completed or aborted. |
| 68 | 68 | // This operation returns at most 1,000 multipart uploads in the response. |
| 69 | 69 | // |
| 70 | −func (api Minio) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) { | |
| 70 | +func (api MinioAPI) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) { | |
| 71 | 71 | // Ticket master block |
| 72 | 72 | { |
| 73 | − op := Operation{} | |
| 73 | + op := APIOperation{} | |
| 74 | 74 | op.ProceedCh = make(chan struct{}) |
| 75 | 75 | api.OP <- op |
| 76 | 76 | // 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 | 133 | // of the objects in a bucket. You can use the request parameters as selection |
| 134 | 134 | // criteria to return a subset of the objects in a bucket. |
| 135 | 135 | // |
| 136 | −func (api Minio) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { | |
| 136 | +func (api MinioAPI) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { | |
| 137 | 137 | // Ticket master block |
| 138 | 138 | { |
| 139 | − op := Operation{} | |
| 139 | + op := APIOperation{} | |
| 140 | 140 | op.ProceedCh = make(chan struct{}) |
| 141 | 141 | api.OP <- op |
| 142 | 142 | // 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 | 209 | // This implementation of the GET operation returns a list of all buckets |
| 210 | 210 | // owned by the authenticated sender of the request. |
| 211 | −func (api Minio) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { | |
| 211 | +func (api MinioAPI) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { | |
| 212 | 212 | // Ticket master block |
| 213 | 213 | { |
| 214 | − op := Operation{} | |
| 214 | + op := APIOperation{} | |
| 215 | 215 | op.ProceedCh = make(chan struct{}) |
| 216 | 216 | api.OP <- op |
| 217 | 217 | // 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 | 260 | // PutBucketHandler - PUT Bucket |
| 261 | 261 | // ---------- |
| 262 | 262 | // This implementation of the PUT operation creates a new bucket for authenticated request |
| 263 | −func (api Minio) PutBucketHandler(w http.ResponseWriter, req *http.Request) { | |
| 263 | +func (api MinioAPI) PutBucketHandler(w http.ResponseWriter, req *http.Request) { | |
| 264 | 264 | // Ticket master block |
| 265 | 265 | { |
| 266 | − op := Operation{} | |
| 266 | + op := APIOperation{} | |
| 267 | 267 | op.ProceedCh = make(chan struct{}) |
| 268 | 268 | api.OP <- op |
| 269 | 269 | // 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 | 338 | // PutBucketACLHandler - PUT Bucket ACL |
| 339 | 339 | // ---------- |
| 340 | 340 | // This implementation of the PUT operation modifies the bucketACL for authenticated request |
| 341 | −func (api Minio) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) { | |
| 341 | +func (api MinioAPI) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) { | |
| 342 | 342 | // Ticket master block |
| 343 | 343 | { |
| 344 | − op := Operation{} | |
| 344 | + op := APIOperation{} | |
| 345 | 345 | op.ProceedCh = make(chan struct{}) |
| 346 | 346 | api.OP <- op |
| 347 | 347 | // 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 | 395 | // The operation returns a 200 OK if the bucket exists and you |
| 396 | 396 | // have permission to access it. Otherwise, the operation might |
| 397 | 397 | // return responses such as 404 Not Found and 403 Forbidden. |
| 398 | −func (api Minio) HeadBucketHandler(w http.ResponseWriter, req *http.Request) { | |
| 398 | +func (api MinioAPI) HeadBucketHandler(w http.ResponseWriter, req *http.Request) { | |
| 399 | 399 | // Ticket master block |
| 400 | 400 | { |
| 401 | − op := Operation{} | |
| 401 | + op := APIOperation{} | |
| 402 | 402 | op.ProceedCh = make(chan struct{}) |
| 403 | 403 | api.OP <- op |
| 404 | 404 | // block until Ticket master gives us a go |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import "net/http" |
| 20 | 20 | |
| @@ -14,12 +14,12 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import "encoding/xml" |
| 20 | 20 | |
| 21 | −// Config - http server config | |
| 22 | −type Config struct { | |
| 21 | +// APIConfig - http server config | |
| 22 | +type APIConfig struct { | |
| 23 | 23 | Address string |
| 24 | 24 | TLS bool |
| 25 | 25 | CertFile string |
| @@ -14,22 +14,22 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "encoding/xml" |
| 21 | 21 | "net/http" |
| 22 | 22 | ) |
| 23 | 23 | |
| 24 | −// Error structure | |
| 25 | −type Error struct { | |
| 24 | +// APIError structure | |
| 25 | +type APIError struct { | |
| 26 | 26 | Code string |
| 27 | 27 | Description string |
| 28 | 28 | HTTPStatusCode int |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | −// ErrorResponse - error response format | |
| 32 | −type ErrorResponse struct { | |
| 31 | +// APIErrorResponse - error response format | |
| 32 | +type APIErrorResponse struct { | |
| 33 | 33 | XMLName xml.Name `xml:"Error" json:"-"` |
| 34 | 34 | Code string |
| 35 | 35 | Message string |
| @@ -77,8 +77,8 @@ const ( · 41 unmodified lines | ||
| 77 | 77 | NotAcceptable = iota + 30 |
| 78 | 78 | ) |
| 79 | 79 | |
| 80 | −// Error code to Error structure map | |
| 81 | −var errorCodeResponse = map[int]Error{ | |
| 80 | +// APIError code to Error structure map | |
| 81 | +var errorCodeResponse = map[int]APIError{ | |
| 82 | 82 | InvalidMaxUploads: { |
| 83 | 83 | Code: "InvalidArgument", |
| 84 | 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 | 234 | // errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown |
| 235 | −func getErrorCode(code int) Error { | |
| 235 | +func getErrorCode(code int) APIError { | |
| 236 | 236 | return errorCodeResponse[code] |
| 237 | 237 | } |
| 238 | 238 | |
| 239 | 239 | // getErrorResponse gets in standard error and resource value and |
| 240 | 240 | // provides a encodable populated response values |
| 241 | −func getErrorResponse(err Error, resource string) ErrorResponse { | |
| 242 | − var data = ErrorResponse{} | |
| 241 | +func getErrorResponse(err APIError, resource string) APIErrorResponse { | |
| 242 | + var data = APIErrorResponse{} | |
| 243 | 243 | data.Code = err.Code |
| 244 | 244 | data.Message = err.Description |
| 245 | 245 | if resource != "" { |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "errors" |
| @@ -44,10 +44,6 @@ type resourceHandler struct { · 23 unmodified lines | ||
| 44 | 44 | handler http.Handler |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | −const ( | |
| 48 | − iso8601Format = "20060102T150405Z" | |
| 49 | −) | |
| 50 | − | |
| 51 | 47 | func parseDate(req *http.Request) (time.Time, error) { |
| 52 | 48 | amzDate := req.Header.Get(http.CanonicalHeaderKey("x-amz-date")) |
| 53 | 49 | switch { |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "bytes" |
| @@ -22,6 +22,7 @@ import ( · 1 unmodified line | ||
| 22 | 22 | "encoding/json" |
| 23 | 23 | "encoding/xml" |
| 24 | 24 | "net/http" |
| 25 | + "runtime" | |
| 25 | 26 | "strconv" |
| 26 | 27 | |
| 27 | 28 | "github.com/minio/minio/pkg/donut" |
| @@ -51,9 +52,7 @@ func generateRequestID() []byte { · 23 unmodified lines | ||
| 51 | 52 | func setCommonHeaders(w http.ResponseWriter, acceptsType string, contentLength int) { |
| 52 | 53 | // set unique request ID for each reply |
| 53 | 54 | w.Header().Set("X-Amz-Request-Id", string(generateRequestID())) |
| 54 | − | |
| 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 + ")")) | |
| 55 | + w.Header().Set("Server", ("Minio/" + minioReleaseTag + " (" + runtime.GOOS + ";" + runtime.GOARCH + ")")) | |
| 57 | 56 | |
| 58 | 57 | w.Header().Set("Accept-Ranges", "bytes") |
| 59 | 58 | w.Header().Set("Content-Type", acceptsType) |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "errors" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "bytes" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| @@ -33,10 +33,10 @@ const ( · 12 unmodified lines | ||
| 33 | 33 | // ---------- |
| 34 | 34 | // This implementation of the GET operation retrieves object. To use GET, |
| 35 | 35 | // you must have READ access to the object. |
| 36 | −func (api Minio) GetObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 36 | +func (api MinioAPI) GetObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 37 | 37 | // ticket master block |
| 38 | 38 | { |
| 39 | − op := Operation{} | |
| 39 | + op := APIOperation{} | |
| 40 | 40 | op.ProceedCh = make(chan struct{}) |
| 41 | 41 | api.OP <- op |
| 42 | 42 | // 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 | 100 | // HeadObjectHandler - HEAD Object |
| 101 | 101 | // ----------- |
| 102 | 102 | // The HEAD operation retrieves metadata from an object without returning the object itself. |
| 103 | −func (api Minio) HeadObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 103 | +func (api MinioAPI) HeadObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 104 | 104 | // ticket master block |
| 105 | 105 | { |
| 106 | − op := Operation{} | |
| 106 | + op := APIOperation{} | |
| 107 | 107 | op.ProceedCh = make(chan struct{}) |
| 108 | 108 | api.OP <- op |
| 109 | 109 | // 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 | 157 | // PutObjectHandler - PUT Object |
| 158 | 158 | // ---------- |
| 159 | 159 | // This implementation of the PUT operation adds an object to a bucket. |
| 160 | −func (api Minio) PutObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 160 | +func (api MinioAPI) PutObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 161 | 161 | // Ticket master block |
| 162 | 162 | { |
| 163 | − op := Operation{} | |
| 163 | + op := APIOperation{} | |
| 164 | 164 | op.ProceedCh = make(chan struct{}) |
| 165 | 165 | api.OP <- op |
| 166 | 166 | // 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 | 259 | /// Multipart API |
| 260 | 260 | |
| 261 | 261 | // NewMultipartUploadHandler - New multipart upload |
| 262 | −func (api Minio) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { | |
| 262 | +func (api MinioAPI) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { | |
| 263 | 263 | // Ticket master block |
| 264 | 264 | { |
| 265 | − op := Operation{} | |
| 265 | + op := APIOperation{} | |
| 266 | 266 | op.ProceedCh = make(chan struct{}) |
| 267 | 267 | api.OP <- op |
| 268 | 268 | // 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 | 319 | // PutObjectPartHandler - Upload part |
| 320 | −func (api Minio) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) { | |
| 320 | +func (api MinioAPI) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) { | |
| 321 | 321 | // Ticket master block |
| 322 | 322 | { |
| 323 | − op := Operation{} | |
| 323 | + op := APIOperation{} | |
| 324 | 324 | op.ProceedCh = make(chan struct{}) |
| 325 | 325 | api.OP <- op |
| 326 | 326 | // 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 | 419 | // AbortMultipartUploadHandler - Abort multipart upload |
| 420 | −func (api Minio) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { | |
| 420 | +func (api MinioAPI) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { | |
| 421 | 421 | // Ticket master block |
| 422 | 422 | { |
| 423 | − op := Operation{} | |
| 423 | + op := APIOperation{} | |
| 424 | 424 | op.ProceedCh = make(chan struct{}) |
| 425 | 425 | api.OP <- op |
| 426 | 426 | // 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 | 469 | // ListObjectPartsHandler - List object parts |
| 470 | −func (api Minio) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request) { | |
| 470 | +func (api MinioAPI) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request) { | |
| 471 | 471 | // Ticket master block |
| 472 | 472 | { |
| 473 | − op := Operation{} | |
| 473 | + op := APIOperation{} | |
| 474 | 474 | op.ProceedCh = make(chan struct{}) |
| 475 | 475 | api.OP <- op |
| 476 | 476 | // 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 | 534 | // CompleteMultipartUploadHandler - Complete multipart upload |
| 535 | −func (api Minio) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { | |
| 535 | +func (api MinioAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { | |
| 536 | 536 | // Ticket master block |
| 537 | 537 | { |
| 538 | − op := Operation{} | |
| 538 | + op := APIOperation{} | |
| 539 | 539 | op.ProceedCh = make(chan struct{}) |
| 540 | 540 | api.OP <- op |
| 541 | 541 | // 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 | 597 | /// Delete API |
| 598 | 598 | |
| 599 | 599 | // DeleteBucketHandler - Delete bucket |
| 600 | −func (api Minio) DeleteBucketHandler(w http.ResponseWriter, req *http.Request) { | |
| 600 | +func (api MinioAPI) DeleteBucketHandler(w http.ResponseWriter, req *http.Request) { | |
| 601 | 601 | error := getErrorCode(MethodNotAllowed) |
| 602 | 602 | w.WriteHeader(error.HTTPStatusCode) |
| 603 | 603 | } |
| 604 | 604 | |
| 605 | 605 | // DeleteObjectHandler - Delete object |
| 606 | −func (api Minio) DeleteObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 606 | +func (api MinioAPI) DeleteObjectHandler(w http.ResponseWriter, req *http.Request) { | |
| 607 | 607 | error := getErrorCode(MethodNotAllowed) |
| 608 | 608 | w.WriteHeader(error.HTTPStatusCode) |
| 609 | 609 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/url" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "errors" |
| @@ -28,6 +28,8 @@ import ( · 7 unmodified lines | ||
| 28 | 28 | |
| 29 | 29 | const ( |
| 30 | 30 | authHeaderPrefix = "AWS4-HMAC-SHA256" |
| 31 | + iso8601Format = "20060102T150405Z" | |
| 32 | + yyyymmdd = "20060102" | |
| 31 | 33 | ) |
| 32 | 34 | |
| 33 | 35 | // getCredentialsFromAuth parse credentials tag from authorization value |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import "errors" |
| 20 | 20 | |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "encoding/base64" |
| @@ -14,30 +14,30 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package api | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import "github.com/minio/minio/pkg/donut" |
| 20 | 20 | |
| 21 | −// Operation container for individual operations read by Ticket Master | |
| 22 | −type Operation struct { | |
| 21 | +// APIOperation container for individual operations read by Ticket Master | |
| 22 | +type APIOperation struct { | |
| 23 | 23 | ProceedCh chan struct{} |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | −// Minio container for API and also carries OP (operation) channel | |
| 27 | −type Minio struct { | |
| 28 | − OP chan Operation | |
| 26 | +// MinioAPI container for API and also carries OP (operation) channel | |
| 27 | +type MinioAPI struct { | |
| 28 | + OP chan APIOperation | |
| 29 | 29 | Donut donut.Interface |
| 30 | 30 | } |
| 31 | 31 | |
| 32 | −// New instantiate a new minio API | |
| 33 | −func New() Minio { | |
| 32 | +// NewAPI instantiate a new minio API | |
| 33 | +func NewAPI() MinioAPI { | |
| 34 | 34 | // ignore errors for now |
| 35 | 35 | d, err := donut.New() |
| 36 | 36 | if err != nil { |
| 37 | 37 | panic(err) |
| 38 | 38 | } |
| 39 | − return Minio{ | |
| 40 | − OP: make(chan Operation), | |
| 39 | + return MinioAPI{ | |
| 40 | + OP: make(chan APIOperation), | |
| 41 | 41 | Donut: d, |
| 42 | 42 | } |
| 43 | 43 | } |
| @@ -16,11 +16,7 @@ · 15 unmodified lines | ||
| 16 | 16 | |
| 17 | 17 | package main |
| 18 | 18 | |
| 19 | −import ( | |
| 20 | − "github.com/minio/cli" | |
| 21 | − "github.com/minio/minio/pkg/server" | |
| 22 | − "github.com/minio/minio/pkg/server/api" | |
| 23 | −) | |
| 19 | +import "github.com/minio/cli" | |
| 24 | 20 | |
| 25 | 21 | var serverCmd = cli.Command{ |
| 26 | 22 | Name: "server", |
| @@ -39,14 +35,14 @@ EXAMPLES: · 12 unmodified lines | ||
| 39 | 35 | `, |
| 40 | 36 | } |
| 41 | 37 | |
| 42 | −func getServerConfig(c *cli.Context) api.Config { | |
| 38 | +func getServerConfig(c *cli.Context) APIConfig { | |
| 43 | 39 | certFile := c.GlobalString("cert") |
| 44 | 40 | keyFile := c.GlobalString("key") |
| 45 | 41 | if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { |
| 46 | 42 | Fatalln("Both certificate and key are required to enable https.") |
| 47 | 43 | } |
| 48 | 44 | tls := (certFile != "" && keyFile != "") |
| 49 | − return api.Config{ | |
| 45 | + return APIConfig{ | |
| 50 | 46 | Address: c.GlobalString("address"), |
| 51 | 47 | TLS: tls, |
| 52 | 48 | CertFile: certFile, |
| @@ -61,6 +57,6 @@ func serverMain(c *cli.Context) { · 8 unmodified lines | ||
| 61 | 57 | } |
| 62 | 58 | |
| 63 | 59 | apiServerConfig := getServerConfig(c) |
| 64 | − err := server.Start(apiServerConfig) | |
| 60 | + err := StartServer(apiServerConfig) | |
| 65 | 61 | errorIf(err.Trace(), "Failed to start the minio server.", nil) |
| 66 | 62 | } |
| @@ -14,17 +14,16 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package server | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "net/http" |
| 21 | 21 | |
| 22 | 22 | router "github.com/gorilla/mux" |
| 23 | − "github.com/minio/minio/pkg/server/api" | |
| 24 | 23 | ) |
| 25 | 24 | |
| 26 | 25 | // registerAPI - register all the object API handlers to their respective paths |
| 27 | −func registerAPI(mux *router.Router, a api.Minio) { | |
| 26 | +func registerAPI(mux *router.Router, a MinioAPI) { | |
| 28 | 27 | mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET") |
| 29 | 28 | mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET") |
| 30 | 29 | mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT") |
| @@ -45,7 +44,7 @@ func registerAPI(mux *router.Router, a api.Minio) { · 14 unmodified lines | ||
| 45 | 44 | mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE") |
| 46 | 45 | } |
| 47 | 46 | |
| 48 | −func registerCustomMiddleware(mux *router.Router, mwHandlers ...api.MiddlewareHandler) http.Handler { | |
| 47 | +func registerCustomMiddleware(mux *router.Router, mwHandlers ...MiddlewareHandler) http.Handler { | |
| 49 | 48 | var f http.Handler |
| 50 | 49 | f = mux |
| 51 | 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 | 56 | // getAPIHandler api handler |
| 58 | −func getAPIHandler(conf api.Config) (http.Handler, api.Minio) { | |
| 59 | − var mwHandlers = []api.MiddlewareHandler{ | |
| 60 | − api.ValidContentTypeHandler, | |
| 61 | − api.TimeValidityHandler, | |
| 62 | − api.IgnoreResourcesHandler, | |
| 63 | − api.ValidateAuthHeaderHandler, | |
| 57 | +func getAPIHandler(conf APIConfig) (http.Handler, MinioAPI) { | |
| 58 | + var mwHandlers = []MiddlewareHandler{ | |
| 59 | + ValidContentTypeHandler, | |
| 60 | + TimeValidityHandler, | |
| 61 | + IgnoreResourcesHandler, | |
| 62 | + ValidateAuthHeaderHandler, | |
| 64 | 63 | // api.LoggingHandler, // Disabled logging until we bring in external logging support |
| 65 | − api.CorsHandler, | |
| 64 | + CorsHandler, | |
| 66 | 65 | } |
| 67 | 66 | |
| 68 | 67 | mux := router.NewRouter() |
| 69 | − minioAPI := api.New() | |
| 68 | + minioAPI := NewAPI() | |
| 70 | 69 | registerAPI(mux, minioAPI) |
| 71 | 70 | apiHandler := registerCustomMiddleware(mux, mwHandlers...) |
| 72 | 71 | return apiHandler, minioAPI |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package server | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "bytes" |
| @@ -28,20 +28,8 @@ import ( · 7 unmodified lines | ||
| 28 | 28 | "regexp" |
| 29 | 29 | "sort" |
| 30 | 30 | "strings" |
| 31 | − "testing" | |
| 32 | 31 | "time" |
| 33 | 32 | "unicode/utf8" |
| 34 | − | |
| 35 | − . "gopkg.in/check.v1" | |
| 36 | −) | |
| 37 | − | |
| 38 | −// Hook up gocheck into the "go test" runner. | |
| 39 | −func Test(t *testing.T) { TestingT(t) } | |
| 40 | − | |
| 41 | −const ( | |
| 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 | 242 | "aws4_request", |
| 255 | 243 | }, "/") |
| 256 | 244 | |
| 257 | − stringToSign := authHeader + "\n" + t.Format(iso8601Format) + "\n" | |
| 245 | + stringToSign := authHeaderPrefix + "\n" + t.Format(iso8601Format) + "\n" | |
| 258 | 246 | stringToSign = stringToSign + scope + "\n" |
| 259 | 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 | 256 | // final Authorization header |
| 269 | 257 | parts := []string{ |
| 270 | − authHeader + " Credential=" + s.accessKeyID + "/" + scope, | |
| 258 | + authHeaderPrefix + " Credential=" + s.accessKeyID + "/" + scope, | |
| 271 | 259 | "SignedHeaders=" + signedHeaders, |
| 272 | 260 | "Signature=" + signature, |
| 273 | 261 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package server | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "crypto/tls" |
| @@ -26,11 +26,10 @@ import ( · 5 unmodified lines | ||
| 26 | 26 | |
| 27 | 27 | "github.com/minio/minio/pkg/minhttp" |
| 28 | 28 | "github.com/minio/minio/pkg/probe" |
| 29 | − "github.com/minio/minio/pkg/server/api" | |
| 30 | 29 | ) |
| 31 | 30 | |
| 32 | 31 | // getAPI server instance |
| 33 | −func getAPIServer(conf api.Config, apiHandler http.Handler) (*http.Server, *probe.Error) { | |
| 32 | +func getAPIServer(conf APIConfig, apiHandler http.Handler) (*http.Server, *probe.Error) { | |
| 34 | 33 | // Minio server config |
| 35 | 34 | httpServer := &http.Server{ |
| 36 | 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 | 85 | // Start ticket master |
| 87 | −func startTM(a api.Minio) { | |
| 86 | +func startTM(a MinioAPI) { | |
| 88 | 87 | for { |
| 89 | 88 | for op := range a.OP { |
| 90 | 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 server | |
| 96 | −func Start(conf api.Config) *probe.Error { | |
| 94 | +// StartServer starts an s3 compatible cloud storage server | |
| 95 | +func StartServer(conf APIConfig) *probe.Error { | |
| 97 | 96 | apiHandler, minioAPI := getAPIHandler(conf) |
| 98 | 97 | apiServer, err := getAPIServer(conf, apiHandler) |
| 99 | 98 | if err != nil { |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package server | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "bytes" |
| @@ -28,7 +28,6 @@ import ( · 7 unmodified lines | ||
| 28 | 28 | "net/http/httptest" |
| 29 | 29 | |
| 30 | 30 | "github.com/minio/minio/pkg/donut" |
| 31 | − "github.com/minio/minio/pkg/server/api" | |
| 32 | 31 | . "gopkg.in/check.v1" |
| 33 | 32 | ) |
| 34 | 33 | |
| @@ -52,7 +51,7 @@ func (s *MyAPIDonutCacheSuite) SetUpSuite(c *C) { · 17 unmodified lines | ||
| 52 | 51 | perr := donut.SaveConfig(conf) |
| 53 | 52 | c.Assert(perr, IsNil) |
| 54 | 53 | |
| 55 | − httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16}) | |
| 54 | + httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) | |
| 56 | 55 | go startTM(minioAPI) |
| 57 | 56 | testAPIDonutCacheServer = httptest.NewServer(httpHandler) |
| 58 | 57 | } |
| @@ -319,7 +318,7 @@ func (s *MyAPIDonutCacheSuite) TestListBuckets(c *C) { · 260 unmodified lines | ||
| 319 | 318 | c.Assert(err, IsNil) |
| 320 | 319 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 321 | 320 | |
| 322 | − var results api.ListBucketsResponse | |
| 321 | + var results ListBucketsResponse | |
| 323 | 322 | decoder := xml.NewDecoder(response.Body) |
| 324 | 323 | err = decoder.Decode(&results) |
| 325 | 324 | c.Assert(err, IsNil) |
| @@ -676,7 +675,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartAbort(c *C) { · 350 unmodified lines | ||
| 676 | 675 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 677 | 676 | |
| 678 | 677 | decoder := xml.NewDecoder(response.Body) |
| 679 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 678 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 680 | 679 | |
| 681 | 680 | err = decoder.Decode(newResponse) |
| 682 | 681 | c.Assert(err, IsNil) |
| @@ -721,7 +720,7 @@ func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) { · 38 unmodified lines | ||
| 721 | 720 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 722 | 721 | |
| 723 | 722 | decoder := xml.NewDecoder(response.Body) |
| 724 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 723 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 725 | 724 | |
| 726 | 725 | err = decoder.Decode(newResponse) |
| 727 | 726 | c.Assert(err, IsNil) |
| @@ -750,7 +749,7 @@ func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) { · 22 unmodified lines | ||
| 750 | 749 | c.Assert(response3.StatusCode, Equals, http.StatusOK) |
| 751 | 750 | |
| 752 | 751 | decoder = xml.NewDecoder(response3.Body) |
| 753 | − newResponse3 := &api.ListMultipartUploadsResponse{} | |
| 752 | + newResponse3 := &ListMultipartUploadsResponse{} | |
| 754 | 753 | err = decoder.Decode(newResponse3) |
| 755 | 754 | c.Assert(err, IsNil) |
| 756 | 755 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") |
| @@ -772,7 +771,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartList(c *C) { · 15 unmodified lines | ||
| 772 | 771 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 773 | 772 | |
| 774 | 773 | decoder := xml.NewDecoder(response.Body) |
| 775 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 774 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 776 | 775 | |
| 777 | 776 | err = decoder.Decode(newResponse) |
| 778 | 777 | c.Assert(err, IsNil) |
| @@ -826,7 +825,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) { · 47 unmodified lines | ||
| 826 | 825 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 827 | 826 | |
| 828 | 827 | decoder := xml.NewDecoder(response.Body) |
| 829 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 828 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 830 | 829 | |
| 831 | 830 | err = decoder.Decode(newResponse) |
| 832 | 831 | c.Assert(err, IsNil) |
| @@ -888,7 +887,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) { · 55 unmodified lines | ||
| 888 | 887 | func verifyError(c *C, response *http.Response, code, description string, statusCode int) { |
| 889 | 888 | data, err := ioutil.ReadAll(response.Body) |
| 890 | 889 | c.Assert(err, IsNil) |
| 891 | − errorResponse := api.ErrorResponse{} | |
| 890 | + errorResponse := APIErrorResponse{} | |
| 892 | 891 | err = xml.Unmarshal(data, &errorResponse) |
| 893 | 892 | c.Assert(err, IsNil) |
| 894 | 893 | c.Assert(errorResponse.Code, Equals, code) |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package server | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "bytes" |
| @@ -29,7 +29,6 @@ import ( · 8 unmodified lines | ||
| 29 | 29 | "net/http/httptest" |
| 30 | 30 | |
| 31 | 31 | "github.com/minio/minio/pkg/donut" |
| 32 | − "github.com/minio/minio/pkg/server/api" | |
| 33 | 32 | . "gopkg.in/check.v1" |
| 34 | 33 | ) |
| 35 | 34 | |
| @@ -71,7 +70,7 @@ func (s *MyAPIDonutSuite) SetUpSuite(c *C) { · 35 unmodified lines | ||
| 71 | 70 | perr := donut.SaveConfig(conf) |
| 72 | 71 | c.Assert(perr, IsNil) |
| 73 | 72 | |
| 74 | − httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16}) | |
| 73 | + httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) | |
| 75 | 74 | go startTM(minioAPI) |
| 76 | 75 | testAPIDonutServer = httptest.NewServer(httpHandler) |
| 77 | 76 | } |
| @@ -338,7 +337,7 @@ func (s *MyAPIDonutSuite) TestListBuckets(c *C) { · 260 unmodified lines | ||
| 338 | 337 | c.Assert(err, IsNil) |
| 339 | 338 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 340 | 339 | |
| 341 | − var results api.ListBucketsResponse | |
| 340 | + var results ListBucketsResponse | |
| 342 | 341 | decoder := xml.NewDecoder(response.Body) |
| 343 | 342 | err = decoder.Decode(&results) |
| 344 | 343 | c.Assert(err, IsNil) |
| @@ -696,7 +695,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartAbort(c *C) { · 351 unmodified lines | ||
| 696 | 695 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 697 | 696 | |
| 698 | 697 | decoder := xml.NewDecoder(response.Body) |
| 699 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 698 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 700 | 699 | |
| 701 | 700 | err = decoder.Decode(newResponse) |
| 702 | 701 | c.Assert(err, IsNil) |
| @@ -741,7 +740,7 @@ func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) { · 38 unmodified lines | ||
| 741 | 740 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 742 | 741 | |
| 743 | 742 | decoder := xml.NewDecoder(response.Body) |
| 744 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 743 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 745 | 744 | |
| 746 | 745 | err = decoder.Decode(newResponse) |
| 747 | 746 | c.Assert(err, IsNil) |
| @@ -770,7 +769,7 @@ func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) { · 22 unmodified lines | ||
| 770 | 769 | c.Assert(response3.StatusCode, Equals, http.StatusOK) |
| 771 | 770 | |
| 772 | 771 | decoder = xml.NewDecoder(response3.Body) |
| 773 | − newResponse3 := &api.ListMultipartUploadsResponse{} | |
| 772 | + newResponse3 := &ListMultipartUploadsResponse{} | |
| 774 | 773 | err = decoder.Decode(newResponse3) |
| 775 | 774 | c.Assert(err, IsNil) |
| 776 | 775 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") |
| @@ -792,7 +791,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartList(c *C) { · 15 unmodified lines | ||
| 792 | 791 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 793 | 792 | |
| 794 | 793 | decoder := xml.NewDecoder(response.Body) |
| 795 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 794 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 796 | 795 | |
| 797 | 796 | err = decoder.Decode(newResponse) |
| 798 | 797 | c.Assert(err, IsNil) |
| @@ -846,7 +845,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipart(c *C) { · 47 unmodified lines | ||
| 846 | 845 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 847 | 846 | |
| 848 | 847 | decoder := xml.NewDecoder(response.Body) |
| 849 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 848 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 850 | 849 | |
| 851 | 850 | err = decoder.Decode(newResponse) |
| 852 | 851 | c.Assert(err, IsNil) |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | * limitations under the License. |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −package server | |
| 17 | +package main | |
| 18 | 18 | |
| 19 | 19 | import ( |
| 20 | 20 | "bytes" |
| @@ -30,7 +30,6 @@ import ( · 9 unmodified lines | ||
| 30 | 30 | |
| 31 | 31 | "github.com/minio/minio/pkg/auth" |
| 32 | 32 | "github.com/minio/minio/pkg/donut" |
| 33 | − "github.com/minio/minio/pkg/server/api" | |
| 34 | 33 | . "gopkg.in/check.v1" |
| 35 | 34 | ) |
| 36 | 35 | |
| @@ -79,7 +78,7 @@ func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) { · 42 unmodified lines | ||
| 79 | 78 | perr = auth.SaveConfig(authConf) |
| 80 | 79 | c.Assert(perr, IsNil) |
| 81 | 80 | |
| 82 | − httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16}) | |
| 81 | + httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) | |
| 83 | 82 | go startTM(minioAPI) |
| 84 | 83 | testSignatureV4Server = httptest.NewServer(httpHandler) |
| 85 | 84 | } |
| @@ -347,7 +346,7 @@ func (s *MyAPISignatureV4Suite) TestListBuckets(c *C) { · 261 unmodified lines | ||
| 347 | 346 | c.Assert(err, IsNil) |
| 348 | 347 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 349 | 348 | |
| 350 | − var results api.ListBucketsResponse | |
| 349 | + var results ListBucketsResponse | |
| 351 | 350 | decoder := xml.NewDecoder(response.Body) |
| 352 | 351 | err = decoder.Decode(&results) |
| 353 | 352 | c.Assert(err, IsNil) |
| @@ -689,7 +688,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipartAbort(c *C) { · 335 unmodified lines | ||
| 689 | 688 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 690 | 689 | |
| 691 | 690 | decoder := xml.NewDecoder(response.Body) |
| 692 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 691 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 693 | 692 | |
| 694 | 693 | err = decoder.Decode(newResponse) |
| 695 | 694 | c.Assert(err, IsNil) |
| @@ -736,7 +735,7 @@ func (s *MyAPISignatureV4Suite) TestBucketMultipartList(c *C) { · 40 unmodified lines | ||
| 736 | 735 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 737 | 736 | |
| 738 | 737 | decoder := xml.NewDecoder(response.Body) |
| 739 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 738 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 740 | 739 | |
| 741 | 740 | err = decoder.Decode(newResponse) |
| 742 | 741 | c.Assert(err, IsNil) |
| @@ -767,7 +766,7 @@ func (s *MyAPISignatureV4Suite) TestBucketMultipartList(c *C) { · 24 unmodified lines | ||
| 767 | 766 | c.Assert(response3.StatusCode, Equals, http.StatusOK) |
| 768 | 767 | |
| 769 | 768 | decoder = xml.NewDecoder(response3.Body) |
| 770 | − newResponse3 := &api.ListMultipartUploadsResponse{} | |
| 769 | + newResponse3 := &ListMultipartUploadsResponse{} | |
| 771 | 770 | err = decoder.Decode(newResponse3) |
| 772 | 771 | c.Assert(err, IsNil) |
| 773 | 772 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") |
| @@ -789,7 +788,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipartList(c *C) { · 15 unmodified lines | ||
| 789 | 788 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 790 | 789 | |
| 791 | 790 | decoder := xml.NewDecoder(response.Body) |
| 792 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 791 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 793 | 792 | |
| 794 | 793 | err = decoder.Decode(newResponse) |
| 795 | 794 | c.Assert(err, IsNil) |
| @@ -845,7 +844,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipart(c *C) { · 49 unmodified lines | ||
| 845 | 844 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 846 | 845 | |
| 847 | 846 | decoder := xml.NewDecoder(response.Body) |
| 848 | − newResponse := &api.InitiateMultipartUploadResponse{} | |
| 847 | + newResponse := &InitiateMultipartUploadResponse{} | |
| 849 | 848 | |
| 850 | 849 | err = decoder.Decode(newResponse) |
| 851 | 850 | c.Assert(err, IsNil) |
| @@ -99,6 +99,34 @@ · 98 unmodified lines | ||
| 99 | 99 | "local": "vendor/gopkg.in/check.v1", |
| 100 | 100 | "revision": "11d3bc7aa68e238947792f30573146a3231fc0f1", |
| 101 | 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 | } |
| @@ -0,0 +1,25 @@ | ||
| 1 | +mgo - MongoDB driver for Go | |
| 2 | + | |
| 3 | +Copyright (c) 2010-2013 - 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. | |
| @@ -0,0 +1,5 @@ | ||
| 1 | +startdb: | |
| 2 | + @testdb/setup.sh start | |
| 3 | + | |
| 4 | +stopdb: | |
| 5 | + @testdb/setup.sh stop | |
| @@ -0,0 +1,4 @@ | ||
| 1 | +The MongoDB driver for Go | |
| 2 | +------------------------- | |
| 3 | + | |
| 4 | +Please go to [http://labix.org/mgo](http://labix.org/mgo) for all project details. | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 41 | +type authCmd struct { | |
| 42 | + Authenticate int | |
| 43 | + | |
| 44 | + Nonce string | |
| 45 | + User string | |
| 46 | + Key string | |
| 47 | +} | |
| 48 | + | |
| 49 | +type startSaslCmd struct { | |
| 50 | + StartSASL int `bson:"startSasl"` | |
| 51 | +} | |
| 52 | + | |
| 53 | +type authResult struct { | |
| 54 | + ErrMsg string | |
| 55 | + Ok bool | |
| 56 | +} | |
| 57 | + | |
| 58 | +type getNonceCmd struct { | |
| 59 | + GetNonce int | |
| 60 | +} | |
| 61 | + | |
| 62 | +type getNonceResult struct { | |
| 63 | + Nonce string | |
| 64 | + Err string "$err" | |
| 65 | + Code int | |
| 66 | +} | |
| 67 | + | |
| 68 | +type logoutCmd struct { | |
| 69 | + Logout int | |
| 70 | +} | |
| 71 | + | |
| 72 | +type 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 | + | |
| 80 | +type 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 | + | |
| 90 | +type saslStepper interface { | |
| 91 | + Step(serverData []byte) (clientData []byte, done bool, err error) | |
| 92 | + Close() | |
| 93 | +} | |
| 94 | + | |
| 95 | +func (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 | + | |
| 115 | +func (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 | + | |
| 161 | +func (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 | + | |
| 204 | +func (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 | + | |
| 238 | +type authX509Cmd struct { | |
| 239 | + Authenticate int | |
| 240 | + User string | |
| 241 | + Mechanism string | |
| 242 | +} | |
| 243 | + | |
| 244 | +func (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 | + | |
| 259 | +func (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 | + | |
| 274 | +func (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 | + | |
| 356 | +func 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 | + | |
| 363 | +type saslScram struct { | |
| 364 | + cred Credential | |
| 365 | + client *scram.Client | |
| 366 | +} | |
| 367 | + | |
| 368 | +func (s *saslScram) Close() {} | |
| 369 | + | |
| 370 | +func (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 | + | |
| 375 | +func (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 | + | |
| 410 | +func (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 | + | |
| 420 | +func (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 | + | |
| 430 | +func (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 | + | |
| 447 | +func (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 | + | |
| 458 | +func (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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo_test | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 45 | +func (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 | + | |
| 69 | +func (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 | + | |
| 97 | +func (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 | + | |
| 124 | +func (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 | + | |
| 148 | +func (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 | + | |
| 169 | +func (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 | + | |
| 250 | +func (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 | + | |
| 283 | +func (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 | + | |
| 343 | +func (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 | + | |
| 374 | +func (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 | + | |
| 401 | +func (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 | + | |
| 422 | +func (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 | + | |
| 440 | +func (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 | + | |
| 459 | +func (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 | + | |
| 486 | +func (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 | + | |
| 515 | +func (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 | + | |
| 531 | +func (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 | + | |
| 548 | +func (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 | + | |
| 565 | +func (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 | + | |
| 582 | +func (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 | + | |
| 633 | +func (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 | + | |
| 685 | +func (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 | + | |
| 723 | +func (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 | + | |
| 732 | +func (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 | + | |
| 741 | +func (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 | + | |
| 760 | +func (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 | + | |
| 789 | +func (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 | + | |
| 812 | +func (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 | + | |
| 828 | +func (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 | + | |
| 848 | +func (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 | + | |
| 880 | +func (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 | + | |
| 897 | +func (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 | + | |
| 966 | +var ( | |
| 967 | + plainFlag = flag.String("plain", "", "Host to test PLAIN authentication against (depends on custom environment)") | |
| 968 | + plainUser = "einstein" | |
| 969 | + plainPass = "password" | |
| 970 | +) | |
| 971 | + | |
| 972 | +func (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 | + | |
| 1003 | +func (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 | + | |
| 1017 | +var ( | |
| 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. | |
| 1027 | +type KerberosSuite struct{} | |
| 1028 | + | |
| 1029 | +var _ = Suite(&KerberosSuite{}) | |
| 1030 | + | |
| 1031 | +func (kerberosSuite *KerberosSuite) SetUpSuite(c *C) { | |
| 1032 | + mgo.SetDebug(true) | |
| 1033 | + mgo.SetStats(true) | |
| 1034 | +} | |
| 1035 | + | |
| 1036 | +func (kerberosSuite *KerberosSuite) TearDownSuite(c *C) { | |
| 1037 | + mgo.SetDebug(false) | |
| 1038 | + mgo.SetStats(false) | |
| 1039 | +} | |
| 1040 | + | |
| 1041 | +func (kerberosSuite *KerberosSuite) SetUpTest(c *C) { | |
| 1042 | + mgo.SetLogger((*cLogger)(c)) | |
| 1043 | + mgo.ResetStats() | |
| 1044 | +} | |
| 1045 | + | |
| 1046 | +func (kerberosSuite *KerberosSuite) TearDownTest(c *C) { | |
| 1047 | + mgo.SetLogger(nil) | |
| 1048 | +} | |
| 1049 | + | |
| 1050 | +func (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 | + | |
| 1078 | +func (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 | + | |
| 1095 | +func (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 | + | |
| 1130 | +func (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` | |
| 1168 | +func 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 | + | |
| 1176 | +func windowsAppendPasswordToCredential(cred *mgo.Credential) { | |
| 1177 | + if runtime.GOOS == "windows" { | |
| 1178 | + cred.Password = getWindowsKerberosPassword() | |
| 1179 | + } | |
| 1180 | +} | |
| @@ -0,0 +1,25 @@ | ||
| 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. | |
| @@ -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. | |
| 33 | +package bson | |
| 34 | + | |
| 35 | +import ( | |
| 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. | |
| 62 | +type 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 | +// | |
| 93 | +type 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. | |
| 100 | +var 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. | |
| 110 | +type 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. | |
| 119 | +type D []DocElem | |
| 120 | + | |
| 121 | +// DocElem is an element of the bson.D document representation. | |
| 122 | +type 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. | |
| 128 | +func (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 | +// | |
| 145 | +type 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. | |
| 154 | +type RawD []RawDocElem | |
| 155 | + | |
| 156 | +// See the RawD type. | |
| 157 | +type 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 | |
| 167 | +type 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. | |
| 172 | +func 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. | |
| 182 | +func 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. | |
| 192 | +var objectIdCounter uint32 = 0 | |
| 193 | + | |
| 194 | +// machineId stores machine id generated once and used in subsequent calls | |
| 195 | +// to NewObjectId function. | |
| 196 | +var 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. | |
| 201 | +func 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. | |
| 219 | +func 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. | |
| 244 | +func 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"). | |
| 252 | +func (id ObjectId) String() string { | |
| 253 | + return fmt.Sprintf(`ObjectIdHex("%x")`, string(id)) | |
| 254 | +} | |
| 255 | + | |
| 256 | +// Hex returns a hex representation of the ObjectId. | |
| 257 | +func (id ObjectId) Hex() string { | |
| 258 | + return hex.EncodeToString([]byte(id)) | |
| 259 | +} | |
| 260 | + | |
| 261 | +// MarshalJSON turns a bson.ObjectId into a json.Marshaller. | |
| 262 | +func (id ObjectId) MarshalJSON() ([]byte, error) { | |
| 263 | + return []byte(fmt.Sprintf(`"%x"`, string(id))), nil | |
| 264 | +} | |
| 265 | + | |
| 266 | +var nullBytes = []byte("null") | |
| 267 | + | |
| 268 | +// UnmarshalJSON turns *bson.ObjectId into a json.Unmarshaller. | |
| 269 | +func (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. | |
| 287 | +func (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. | |
| 293 | +func (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. | |
| 302 | +func (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. | |
| 310 | +func (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. | |
| 316 | +func (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. | |
| 322 | +func (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. | |
| 330 | +type 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. | |
| 337 | +func 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. | |
| 343 | +type MongoTimestamp int64 | |
| 344 | + | |
| 345 | +type orderKey int64 | |
| 346 | + | |
| 347 | +// MaxKey is a special value that compares higher than all other possible BSON | |
| 348 | +// values in a MongoDB database. | |
| 349 | +var 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. | |
| 353 | +var MinKey = orderKey(-1 << 63) | |
| 354 | + | |
| 355 | +type undefined struct{} | |
| 356 | + | |
| 357 | +// Undefined represents the undefined BSON value. | |
| 358 | +var 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 | +// | |
| 370 | +type 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. | |
| 383 | +type 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. | |
| 391 | +type 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. | |
| 400 | +type DBPointer struct { | |
| 401 | + Namespace string | |
| 402 | + Id ObjectId | |
| 403 | +} | |
| 404 | + | |
| 405 | +const initialBufferSize = 64 | |
| 406 | + | |
| 407 | +func 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 | +// | |
| 458 | +func 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. | |
| 499 | +func 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. | |
| 526 | +func (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 | + | |
| 547 | +type TypeError struct { | |
| 548 | + Type reflect.Type | |
| 549 | + Kind byte | |
| 550 | +} | |
| 551 | + | |
| 552 | +func (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 | + | |
| 559 | +type structInfo struct { | |
| 560 | + FieldsMap map[string]fieldInfo | |
| 561 | + FieldsList []fieldInfo | |
| 562 | + InlineMap int | |
| 563 | + Zero reflect.Value | |
| 564 | +} | |
| 565 | + | |
| 566 | +type fieldInfo struct { | |
| 567 | + Key string | |
| 568 | + Num int | |
| 569 | + OmitEmpty bool | |
| 570 | + MinSize bool | |
| 571 | + Inline []int | |
| 572 | +} | |
| 573 | + | |
| 574 | +var structMap = make(map[reflect.Type]*structInfo) | |
| 575 | +var structMapMutex sync.RWMutex | |
| 576 | + | |
| 577 | +type externalPanic string | |
| 578 | + | |
| 579 | +func (e externalPanic) String() string { | |
| 580 | + return string(e) | |
| 581 | +} | |
| 582 | + | |
| 583 | +func 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 | +} | |
| @@ -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 | + | |
| 28 | +package bson_test | |
| 29 | + | |
| 30 | +import ( | |
| 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 | + | |
| 43 | +func TestAll(t *testing.T) { | |
| 44 | + TestingT(t) | |
| 45 | +} | |
| 46 | + | |
| 47 | +type S struct{} | |
| 48 | + | |
| 49 | +var _ = 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. | |
| 53 | +func 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 | + | |
| 60 | +func 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 | + | |
| 78 | +func 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 | + | |
| 85 | +type testItemType struct { | |
| 86 | + obj interface{} | |
| 87 | + data string | |
| 88 | +} | |
| 89 | + | |
| 90 | +// -------------------------------------------------------------------------- | |
| 91 | +// Samples from bsonspec.org: | |
| 92 | + | |
| 93 | +var 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 | + | |
| 101 | +func (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 | + | |
| 109 | +func (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 | + | |
| 123 | +var 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 | + | |
| 175 | +func (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 | + | |
| 183 | +func (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 | + | |
| 192 | +func (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 | + | |
| 210 | +func (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 | + | |
| 216 | +func (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 | + | |
| 227 | +func (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 | + | |
| 236 | +func (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 | + | |
| 251 | +var 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 | + | |
| 315 | +func (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 | + | |
| 328 | +type specSample1 struct { | |
| 329 | + Hello string | |
| 330 | +} | |
| 331 | + | |
| 332 | +type specSample2 struct { | |
| 333 | + BSON []interface{} "BSON" | |
| 334 | +} | |
| 335 | + | |
| 336 | +var 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 | + | |
| 344 | +func (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 | + | |
| 353 | +func (s *S) TestUnmarshalStructSampleItems(c *C) { | |
| 354 | + for _, item := range structSampleItems { | |
| 355 | + testUnmarshal(c, item.data, item.obj) | |
| 356 | + } | |
| 357 | +} | |
| 358 | + | |
| 359 | +func (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 | + | |
| 376 | +var bytevar = byte(8) | |
| 377 | +var byteptr = &bytevar | |
| 378 | + | |
| 379 | +var 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 | + | |
| 416 | +func (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 | + | |
| 425 | +func (s *S) TestUnmarshalStructItems(c *C) { | |
| 426 | + for _, item := range structItems { | |
| 427 | + testUnmarshal(c, wrapInDoc(item.data), item.obj) | |
| 428 | + } | |
| 429 | +} | |
| 430 | + | |
| 431 | +func (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 | + | |
| 441 | +func (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 | + | |
| 452 | +type dOnIface struct { | |
| 453 | + D interface{} | |
| 454 | +} | |
| 455 | + | |
| 456 | +type ignoreField struct { | |
| 457 | + Before string | |
| 458 | + Ignore string `bson:"-"` | |
| 459 | + After string | |
| 460 | +} | |
| 461 | + | |
| 462 | +var 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 | + | |
| 488 | +func (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 | + | |
| 499 | +var 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 | + | |
| 551 | +func (s *S) TestUnmarshalOneWayItems(c *C) { | |
| 552 | + for _, item := range unmarshalItems { | |
| 553 | + testUnmarshal(c, wrapInDoc(item.data), item.obj) | |
| 554 | + } | |
| 555 | +} | |
| 556 | + | |
| 557 | +func (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 | + | |
| 569 | +type structWithDupKeys struct { | |
| 570 | + Name byte | |
| 571 | + Other byte "name" // Tag should precede. | |
| 572 | +} | |
| 573 | + | |
| 574 | +var 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 | + | |
| 599 | +func (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 | + | |
| 610 | +type unmarshalErrorType struct { | |
| 611 | + obj interface{} | |
| 612 | + data string | |
| 613 | + error string | |
| 614 | +} | |
| 615 | + | |
| 616 | +var 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 | + | |
| 640 | +func (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 | + | |
| 657 | +type unmarshalRawErrorType struct { | |
| 658 | + obj interface{} | |
| 659 | + raw bson.Raw | |
| 660 | + error string | |
| 661 | +} | |
| 662 | + | |
| 663 | +var 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 | + | |
| 682 | +func (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 | + | |
| 689 | +var 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 | + | |
| 709 | +func (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 | + | |
| 722 | +var setterResult = map[string]error{} | |
| 723 | + | |
| 724 | +type setterType struct { | |
| 725 | + received interface{} | |
| 726 | +} | |
| 727 | + | |
| 728 | +func (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 | + | |
| 741 | +type ptrSetterDoc struct { | |
| 742 | + Field *setterType "_" | |
| 743 | +} | |
| 744 | + | |
| 745 | +type valSetterDoc struct { | |
| 746 | + Field setterType "_" | |
| 747 | +} | |
| 748 | + | |
| 749 | +func (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 | + | |
| 780 | +func (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 | + | |
| 787 | +func (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 | + | |
| 811 | +func (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 | + | |
| 829 | +func (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 | + | |
| 834 | +func (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 | + | |
| 853 | +type typeWithGetter struct { | |
| 854 | + result interface{} | |
| 855 | + err error | |
| 856 | +} | |
| 857 | + | |
| 858 | +func (t *typeWithGetter) GetBSON() (interface{}, error) { | |
| 859 | + if t == nil { | |
| 860 | + return "<value is nil>", nil | |
| 861 | + } | |
| 862 | + return t.result, t.err | |
| 863 | +} | |
| 864 | + | |
| 865 | +type docWithGetterField struct { | |
| 866 | + Field *typeWithGetter "_" | |
| 867 | +} | |
| 868 | + | |
| 869 | +func (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 | + | |
| 883 | +func (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 | + | |
| 890 | +func (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 | + | |
| 905 | +type intGetter int64 | |
| 906 | + | |
| 907 | +func (t intGetter) GetBSON() (interface{}, error) { | |
| 908 | + return int64(t), nil | |
| 909 | +} | |
| 910 | + | |
| 911 | +type typeWithIntGetter struct { | |
| 912 | + V intGetter ",minsize" | |
| 913 | +} | |
| 914 | + | |
| 915 | +func (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 | + | |
| 925 | +func (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 | + | |
| 938 | +type crossTypeItem struct { | |
| 939 | + obj1 interface{} | |
| 940 | + obj2 interface{} | |
| 941 | +} | |
| 942 | + | |
| 943 | +type condStr struct { | |
| 944 | + V string ",omitempty" | |
| 945 | +} | |
| 946 | +type condStrNS struct { | |
| 947 | + V string `a:"A" bson:",omitempty" b:"B"` | |
| 948 | +} | |
| 949 | +type condBool struct { | |
| 950 | + V bool ",omitempty" | |
| 951 | +} | |
| 952 | +type condInt struct { | |
| 953 | + V int ",omitempty" | |
| 954 | +} | |
| 955 | +type condUInt struct { | |
| 956 | + V uint ",omitempty" | |
| 957 | +} | |
| 958 | +type condFloat struct { | |
| 959 | + V float64 ",omitempty" | |
| 960 | +} | |
| 961 | +type condIface struct { | |
| 962 | + V interface{} ",omitempty" | |
| 963 | +} | |
| 964 | +type condPtr struct { | |
| 965 | + V *bool ",omitempty" | |
| 966 | +} | |
| 967 | +type condSlice struct { | |
| 968 | + V []string ",omitempty" | |
| 969 | +} | |
| 970 | +type condMap struct { | |
| 971 | + V map[string]int ",omitempty" | |
| 972 | +} | |
| 973 | +type namedCondStr struct { | |
| 974 | + V string "myv,omitempty" | |
| 975 | +} | |
| 976 | +type condTime struct { | |
| 977 | + V time.Time ",omitempty" | |
| 978 | +} | |
| 979 | +type condStruct struct { | |
| 980 | + V struct{ A []int } ",omitempty" | |
| 981 | +} | |
| 982 | + | |
| 983 | +type shortInt struct { | |
| 984 | + V int64 ",minsize" | |
| 985 | +} | |
| 986 | +type shortUint struct { | |
| 987 | + V uint64 ",minsize" | |
| 988 | +} | |
| 989 | +type shortIface struct { | |
| 990 | + V interface{} ",minsize" | |
| 991 | +} | |
| 992 | +type shortPtr struct { | |
| 993 | + V *int64 ",minsize" | |
| 994 | +} | |
| 995 | +type shortNonEmptyInt struct { | |
| 996 | + V int64 ",minsize,omitempty" | |
| 997 | +} | |
| 998 | + | |
| 999 | +type inlineInt struct { | |
| 1000 | + V struct{ A, B int } ",inline" | |
| 1001 | +} | |
| 1002 | +type inlineCantPtr struct { | |
| 1003 | + V *struct{ A, B int } ",inline" | |
| 1004 | +} | |
| 1005 | +type inlineDupName struct { | |
| 1006 | + A int | |
| 1007 | + V struct{ A, B int } ",inline" | |
| 1008 | +} | |
| 1009 | +type inlineMap struct { | |
| 1010 | + A int | |
| 1011 | + M map[string]interface{} ",inline" | |
| 1012 | +} | |
| 1013 | +type inlineMapInt struct { | |
| 1014 | + A int | |
| 1015 | + M map[string]int ",inline" | |
| 1016 | +} | |
| 1017 | +type inlineMapMyM struct { | |
| 1018 | + A int | |
| 1019 | + M MyM ",inline" | |
| 1020 | +} | |
| 1021 | +type inlineDupMap struct { | |
| 1022 | + M1 map[string]interface{} ",inline" | |
| 1023 | + M2 map[string]interface{} ",inline" | |
| 1024 | +} | |
| 1025 | +type inlineBadKeyMap struct { | |
| 1026 | + M map[int]int ",inline" | |
| 1027 | +} | |
| 1028 | + | |
| 1029 | +type getterSetterD bson.D | |
| 1030 | + | |
| 1031 | +func (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 | + | |
| 1038 | +func (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 | + | |
| 1046 | +type getterSetterInt int | |
| 1047 | + | |
| 1048 | +func (i getterSetterInt) GetBSON() (interface{}, error) { | |
| 1049 | + return bson.D{{"a", int(i)}}, nil | |
| 1050 | +} | |
| 1051 | + | |
| 1052 | +func (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 | + | |
| 1059 | +type ifaceType interface { | |
| 1060 | + Hello() | |
| 1061 | +} | |
| 1062 | + | |
| 1063 | +type ifaceSlice []ifaceType | |
| 1064 | + | |
| 1065 | +func (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 | + | |
| 1074 | +func (s ifaceSlice) GetBSON() (interface{}, error) { | |
| 1075 | + return []int{len(s)}, nil | |
| 1076 | +} | |
| 1077 | + | |
| 1078 | +type ( | |
| 1079 | + MyString string | |
| 1080 | + MyBytes []byte | |
| 1081 | + MyBool bool | |
| 1082 | + MyD []bson.DocElem | |
| 1083 | + MyRawD []bson.RawDocElem | |
| 1084 | + MyM map[string]interface{} | |
| 1085 | +) | |
| 1086 | + | |
| 1087 | +var ( | |
| 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 | + | |
| 1099 | +func 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! | |
| 1111 | +var 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). | |
| 1310 | +var 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 | + | |
| 1329 | +func 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 | + | |
| 1342 | +func (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 | + | |
| 1349 | +func (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 | + | |
| 1358 | +func (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 | + | |
| 1364 | +func (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 | + | |
| 1382 | +type objectIdParts struct { | |
| 1383 | + id bson.ObjectId | |
| 1384 | + timestamp int64 | |
| 1385 | + machine []byte | |
| 1386 | + pid uint16 | |
| 1387 | + counter int32 | |
| 1388 | +} | |
| 1389 | + | |
| 1390 | +var 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 | + | |
| 1414 | +func (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 | + | |
| 1424 | +func (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 | + | |
| 1436 | +func (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 | + | |
| 1464 | +func (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 | + | |
| 1476 | +type jsonType struct { | |
| 1477 | + Id bson.ObjectId | |
| 1478 | +} | |
| 1479 | + | |
| 1480 | +var 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 | + | |
| 1513 | +func (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 | + | |
| 1541 | +type BenchT struct { | |
| 1542 | + A, B, C, D, E, F string | |
| 1543 | +} | |
| 1544 | + | |
| 1545 | +type BenchRawT struct { | |
| 1546 | + A string | |
| 1547 | + B int | |
| 1548 | + C bson.M | |
| 1549 | + D []float64 | |
| 1550 | +} | |
| 1551 | + | |
| 1552 | +func (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 | + | |
| 1567 | +func (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 | + | |
| 1582 | +func (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 | +} | |
| @@ -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 | + | |
| 28 | +package bson | |
| 29 | + | |
| 30 | +import ( | |
| 31 | + "fmt" | |
| 32 | + "math" | |
| 33 | + "net/url" | |
| 34 | + "reflect" | |
| 35 | + "strconv" | |
| 36 | + "sync" | |
| 37 | + "time" | |
| 38 | +) | |
| 39 | + | |
| 40 | +type decoder struct { | |
| 41 | + in []byte | |
| 42 | + i int | |
| 43 | + docType reflect.Type | |
| 44 | +} | |
| 45 | + | |
| 46 | +var typeM = reflect.TypeOf(M{}) | |
| 47 | + | |
| 48 | +func newDecoder(in []byte) *decoder { | |
| 49 | + return &decoder{in, 0, typeM} | |
| 50 | +} | |
| 51 | + | |
| 52 | +// -------------------------------------------------------------------------- | |
| 53 | +// Some helper functions. | |
| 54 | + | |
| 55 | +func corrupted() { | |
| 56 | + panic("Document is corrupted") | |
| 57 | +} | |
| 58 | + | |
| 59 | +func 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 | + | |
| 69 | +const ( | |
| 70 | + setterUnknown = iota | |
| 71 | + setterNone | |
| 72 | + setterType | |
| 73 | + setterAddr | |
| 74 | +) | |
| 75 | + | |
| 76 | +var setterStyles map[reflect.Type]int | |
| 77 | +var setterIface reflect.Type | |
| 78 | +var setterMutex sync.RWMutex | |
| 79 | + | |
| 80 | +func init() { | |
| 81 | + var iface Setter | |
| 82 | + setterIface = reflect.TypeOf(&iface).Elem() | |
| 83 | + setterStyles = make(map[reflect.Type]int) | |
| 84 | +} | |
| 85 | + | |
| 86 | +func 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 | + | |
| 105 | +func 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 | + | |
| 121 | +func clearMap(m reflect.Value) { | |
| 122 | + var none reflect.Value | |
| 123 | + for _, k := range m.MapKeys() { | |
| 124 | + m.SetMapIndex(k, none) | |
| 125 | + } | |
| 126 | +} | |
| 127 | + | |
| 128 | +func (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 | + | |
| 290 | +func (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 | + | |
| 325 | +func (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 | + | |
| 364 | +var typeSlice = reflect.TypeOf([]interface{}{}) | |
| 365 | +var typeIface = typeSlice.Elem() | |
| 366 | + | |
| 367 | +func (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 | + | |
| 384 | +func (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 | + | |
| 401 | +func (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 | + | |
| 427 | +var blackHole = settableValueOf(struct{}{}) | |
| 428 | + | |
| 429 | +func (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. | |
| 436 | +func (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 | + | |
| 734 | +func (d *decoder) readRegEx() RegEx { | |
| 735 | + re := RegEx{} | |
| 736 | + re.Pattern = d.readCStr() | |
| 737 | + re.Options = d.readCStr() | |
| 738 | + return re | |
| 739 | +} | |
| 740 | + | |
| 741 | +func (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 | + | |
| 753 | +func (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 | + | |
| 762 | +func (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 | + | |
| 778 | +func (d *decoder) readBool() bool { | |
| 779 | + if d.readByte() == 1 { | |
| 780 | + return true | |
| 781 | + } | |
| 782 | + return false | |
| 783 | +} | |
| 784 | + | |
| 785 | +func (d *decoder) readFloat64() float64 { | |
| 786 | + return math.Float64frombits(uint64(d.readInt64())) | |
| 787 | +} | |
| 788 | + | |
| 789 | +func (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 | + | |
| 797 | +func (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 | + | |
| 809 | +func (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 | + | |
| 818 | +func (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 | +} | |
| @@ -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 | + | |
| 28 | +package bson | |
| 29 | + | |
| 30 | +import ( | |
| 31 | + "encoding/json" | |
| 32 | + "fmt" | |
| 33 | + "math" | |
| 34 | + "net/url" | |
| 35 | + "reflect" | |
| 36 | + "strconv" | |
| 37 | + "time" | |
| 38 | +) | |
| 39 | + | |
| 40 | +// -------------------------------------------------------------------------- | |
| 41 | +// Some internal infrastructure. | |
| 42 | + | |
| 43 | +var ( | |
| 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 | + | |
| 59 | +const itoaCacheSize = 32 | |
| 60 | + | |
| 61 | +var itoaCache []string | |
| 62 | + | |
| 63 | +func init() { | |
| 64 | + itoaCache = make([]string, itoaCacheSize) | |
| 65 | + for i := 0; i != itoaCacheSize; i++ { | |
| 66 | + itoaCache[i] = strconv.Itoa(i) | |
| 67 | + } | |
| 68 | +} | |
| 69 | + | |
| 70 | +func 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 | + | |
| 80 | +type encoder struct { | |
| 81 | + out []byte | |
| 82 | +} | |
| 83 | + | |
| 84 | +func (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 | + | |
| 127 | +func (e *encoder) addMap(v reflect.Value) { | |
| 128 | + for _, k := range v.MapKeys() { | |
| 129 | + e.addElem(k.String(), v.MapIndex(k), false) | |
| 130 | + } | |
| 131 | +} | |
| 132 | + | |
| 133 | +func (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 | + | |
| 164 | +func 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 | + | |
| 200 | +func (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 | + | |
| 238 | +func (e *encoder) addElemName(kind byte, name string) { | |
| 239 | + e.addBytes(kind) | |
| 240 | + e.addBytes([]byte(name)...) | |
| 241 | + e.addBytes(0) | |
| 242 | +} | |
| 243 | + | |
| 244 | +func (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 | + | |
| 450 | +func (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 | + | |
| 463 | +func (e *encoder) addStr(v string) { | |
| 464 | + e.addInt32(int32(len(v) + 1)) | |
| 465 | + e.addCStr(v) | |
| 466 | +} | |
| 467 | + | |
| 468 | +func (e *encoder) addCStr(v string) { | |
| 469 | + e.addBytes([]byte(v)...) | |
| 470 | + e.addBytes(0) | |
| 471 | +} | |
| 472 | + | |
| 473 | +func (e *encoder) reserveInt32() (pos int) { | |
| 474 | + pos = len(e.out) | |
| 475 | + e.addBytes(0, 0, 0, 0) | |
| 476 | + return pos | |
| 477 | +} | |
| 478 | + | |
| 479 | +func (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 | + | |
| 486 | +func (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 | + | |
| 491 | +func (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 | + | |
| 497 | +func (e *encoder) addFloat64(v float64) { | |
| 498 | + e.addInt64(int64(math.Float64bits(v))) | |
| 499 | +} | |
| 500 | + | |
| 501 | +func (e *encoder) addBytes(v ...byte) { | |
| 502 | + e.out = append(e.out, v...) | |
| 503 | +} | |
| @@ -0,0 +1,71 @@ | ||
| 1 | +package 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 | +// | |
| 12 | +type 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. | |
| 22 | +type bulkError struct { | |
| 23 | + err error | |
| 24 | +} | |
| 25 | + | |
| 26 | +// BulkResult holds the results for a bulk operation. | |
| 27 | +type 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 | + | |
| 34 | +func (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 | +// | |
| 42 | +func (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. | |
| 51 | +func (b *Bulk) Unordered() { | |
| 52 | + b.ordered = false | |
| 53 | +} | |
| 54 | + | |
| 55 | +// Insert queues up the provided documents for insertion. | |
| 56 | +func (b *Bulk) Insert(docs ...interface{}) { | |
| 57 | + b.inserts = append(b.inserts, docs...) | |
| 58 | +} | |
| 59 | + | |
| 60 | +// Run runs all the operations queued up. | |
| 61 | +func (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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo_test | |
| 28 | + | |
| 29 | +import ( | |
| 30 | + . "gopkg.in/check.v1" | |
| 31 | + "gopkg.in/mgo.v2" | |
| 32 | +) | |
| 33 | + | |
| 34 | +func (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 | + | |
| 54 | +func (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 | + | |
| 74 | +func (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 | + | |
| 95 | +func (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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 46 | +type 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 | + | |
| 64 | +func 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. | |
| 81 | +func (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. | |
| 90 | +func (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 | + | |
| 108 | +func (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 | + | |
| 117 | +func (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 | + | |
| 129 | +type 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 | + | |
| 141 | +func (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 | + | |
| 150 | +type possibleTimeout interface { | |
| 151 | + Timeout() bool | |
| 152 | +} | |
| 153 | + | |
| 154 | +var syncSocketTimeout = 5 * time.Second | |
| 155 | + | |
| 156 | +func (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 | + | |
| 247 | +type syncKind bool | |
| 248 | + | |
| 249 | +const ( | |
| 250 | + completeSync syncKind = true | |
| 251 | + partialSync syncKind = false | |
| 252 | +) | |
| 253 | + | |
| 254 | +func (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 | + | |
| 291 | +func (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. | |
| 320 | +func (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. | |
| 329 | +const syncServersDelay = 30 * time.Second | |
| 330 | +const 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. | |
| 340 | +func (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 | + | |
| 400 | +func (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 | + | |
| 410 | +func 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 | + | |
| 425 | +type pendingAdd struct { | |
| 426 | + server *mongoServer | |
| 427 | + info *mongoServerInfo | |
| 428 | +} | |
| 429 | + | |
| 430 | +func (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. | |
| 534 | +func (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 | + | |
| 606 | +func (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 | + | |
| 619 | +func (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 | + | |
| 628 | +func (cluster *mongoCluster) ResetIndexCache() { | |
| 629 | + cluster.Lock() | |
| 630 | + cluster.cachedIndex = make(map[string]bool) | |
| 631 | + cluster.Unlock() | |
| 632 | +} | |
| @@ -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 | + | |
| 27 | +package mgo_test | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 42 | +func (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 | + | |
| 96 | +func (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 | + | |
| 161 | +func (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 | + | |
| 198 | +func (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 | + | |
| 241 | +func (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 | + | |
| 274 | +func (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 | + | |
| 314 | +func (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 | + | |
| 359 | +func (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 | + | |
| 396 | +func (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 | + | |
| 434 | +func (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 | + | |
| 475 | +func (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 | + | |
| 526 | +func (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 | + | |
| 569 | +func (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 | + | |
| 648 | +func (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 | + | |
| 685 | +func (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. | |
| 745 | +func (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 | + | |
| 768 | +func (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 | + | |
| 799 | +func (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 | + | |
| 822 | +func (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 | + | |
| 841 | +func (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 | + | |
| 864 | +func (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 | + | |
| 885 | +func (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 | + | |
| 910 | +func (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 | + | |
| 973 | +func (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 | + | |
| 1017 | +func (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 | + | |
| 1043 | +func (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 | + | |
| 1058 | +type OpCounters struct { | |
| 1059 | + Insert int | |
| 1060 | + Query int | |
| 1061 | + Update int | |
| 1062 | + Delete int | |
| 1063 | + GetMore int | |
| 1064 | + Command int | |
| 1065 | +} | |
| 1066 | + | |
| 1067 | +func 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 | + | |
| 1079 | +func (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 | + | |
| 1147 | +func (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 | + | |
| 1215 | +func (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 | + | |
| 1252 | +func (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 | + | |
| 1296 | +func (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 | + | |
| 1339 | +func (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 | + | |
| 1374 | +func (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 | + | |
| 1408 | +func (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 | + | |
| 1462 | +func (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 | + | |
| 1525 | +func (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 | + | |
| 1545 | +func (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 | + | |
| 1571 | +func (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 | +} | |
| @@ -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 | +// | |
| 31 | +package mgo | |
| @@ -0,0 +1,33 @@ | ||
| 1 | +package mgo | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "time" | |
| 5 | +) | |
| 6 | + | |
| 7 | +func 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 | + | |
| 21 | +func 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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 42 | +type GridFS struct { | |
| 43 | + Files *Collection | |
| 44 | + Chunks *Collection | |
| 45 | +} | |
| 46 | + | |
| 47 | +type gfsFileMode int | |
| 48 | + | |
| 49 | +const ( | |
| 50 | + gfsClosed gfsFileMode = 0 | |
| 51 | + gfsReading gfsFileMode = 1 | |
| 52 | + gfsWriting gfsFileMode = 2 | |
| 53 | +) | |
| 54 | + | |
| 55 | +type 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 | + | |
| 75 | +type 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 | + | |
| 86 | +type gfsChunk struct { | |
| 87 | + Id interface{} "_id" | |
| 88 | + FilesId interface{} "files_id" | |
| 89 | + N int | |
| 90 | + Data []byte | |
| 91 | +} | |
| 92 | + | |
| 93 | +type gfsCachedChunk struct { | |
| 94 | + wait sync.Mutex | |
| 95 | + n int | |
| 96 | + data []byte | |
| 97 | + err error | |
| 98 | +} | |
| 99 | + | |
| 100 | +func newGridFS(db *Database, prefix string) *GridFS { | |
| 101 | + return &GridFS{db.C(prefix + ".files"), db.C(prefix + ".chunks")} | |
| 102 | +} | |
| 103 | + | |
| 104 | +func (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 | + | |
| 111 | +func 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 | +// | |
| 153 | +func (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 | +// | |
| 197 | +func (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 | +// | |
| 241 | +func (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 | +// | |
| 280 | +func (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 | +// | |
| 312 | +func (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. | |
| 317 | +func (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 | + | |
| 326 | +type gfsDocId struct { | |
| 327 | + Id interface{} "_id" | |
| 328 | +} | |
| 329 | + | |
| 330 | +// Remove deletes all files with the provided name from the GridFS. | |
| 331 | +func (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 | + | |
| 345 | +func (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. | |
| 366 | +func (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. | |
| 375 | +func (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. | |
| 383 | +func (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. | |
| 392 | +func (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. | |
| 401 | +func (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. | |
| 410 | +func (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. | |
| 419 | +func (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 | +// | |
| 437 | +func (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. | |
| 454 | +func (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. | |
| 467 | +func (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. | |
| 475 | +func (file *GridFile) MD5() (md5 string) { | |
| 476 | + return file.doc.MD5 | |
| 477 | +} | |
| 478 | + | |
| 479 | +// UploadDate returns the file upload time. | |
| 480 | +func (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. | |
| 488 | +func (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. | |
| 501 | +func (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 | + | |
| 519 | +func (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. | |
| 544 | +func (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. | |
| 562 | +func (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 | + | |
| 609 | +func (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. | |
| 652 | +func (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. | |
| 699 | +func (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 | + | |
| 721 | +func (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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo_test | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 39 | +func (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 | + | |
| 110 | +func (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 | + | |
| 186 | +func (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 | + | |
| 214 | +func (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 | + | |
| 295 | +func (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 | + | |
| 332 | +func (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 | + | |
| 360 | +func (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 | + | |
| 377 | +func (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 | + | |
| 414 | +func (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 | + | |
| 474 | +func (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 | + | |
| 504 | +func (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 | + | |
| 583 | +func (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 | + | |
| 621 | +func (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 | + | |
| 651 | +func (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 | +} | |
| @@ -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 | + | |
| 8 | +static 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 | + | |
| 32 | +typedef int (*callback)(void); | |
| 33 | + | |
| 34 | +static 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 | + | |
| 43 | +sasl_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 | +} | |
| @@ -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 | + | |
| 8 | +package 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 | +// | |
| 19 | +import "C" | |
| 20 | + | |
| 21 | +import ( | |
| 22 | + "fmt" | |
| 23 | + "strings" | |
| 24 | + "sync" | |
| 25 | + "unsafe" | |
| 26 | +) | |
| 27 | + | |
| 28 | +type saslStepper interface { | |
| 29 | + Step(serverData []byte) (clientData []byte, done bool, err error) | |
| 30 | + Close() | |
| 31 | +} | |
| 32 | + | |
| 33 | +type 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 | + | |
| 42 | +var initError error | |
| 43 | +var initOnce sync.Once | |
| 44 | + | |
| 45 | +func 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 | + | |
| 52 | +func 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 | + | |
| 74 | +func (ss *saslSession) cstr(s string) *C.char { | |
| 75 | + cstr := C.CString(s) | |
| 76 | + ss.cstrings = append(ss.cstrings, cstr) | |
| 77 | + return cstr | |
| 78 | +} | |
| 79 | + | |
| 80 | +func (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 | + | |
| 98 | +func (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 | + | |
| 130 | +func 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 | +} | |
| @@ -0,0 +1,118 @@ | ||
| 1 | +#include "sasl_windows.h" | |
| 2 | + | |
| 3 | +static const LPSTR SSPI_PACKAGE_NAME = "kerberos"; | |
| 4 | + | |
| 5 | +SECURITY_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 | + | |
| 20 | +int 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 | + | |
| 67 | +int 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 | +} | |
| @@ -0,0 +1,140 @@ | ||
| 1 | +package sasl | |
| 2 | + | |
| 3 | +// #include "sasl_windows.h" | |
| 4 | +import "C" | |
| 5 | + | |
| 6 | +import ( | |
| 7 | + "fmt" | |
| 8 | + "strings" | |
| 9 | + "sync" | |
| 10 | + "unsafe" | |
| 11 | +) | |
| 12 | + | |
| 13 | +type saslStepper interface { | |
| 14 | + Step(serverData []byte) (clientData []byte, done bool, err error) | |
| 15 | + Close() | |
| 16 | +} | |
| 17 | + | |
| 18 | +type 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 | + | |
| 41 | +var initError error | |
| 42 | +var initOnce sync.Once | |
| 43 | + | |
| 44 | +func initSSPI() { | |
| 45 | + rc := C.load_secur32_dll() | |
| 46 | + if rc != 0 { | |
| 47 | + initError = fmt.Errorf("Error loading libraries: %v", rc) | |
| 48 | + } | |
| 49 | +} | |
| 50 | + | |
| 51 | +func 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 | + | |
| 85 | +func (ss *saslSession) cstr(s string) *C.char { | |
| 86 | + cstr := C.CString(s) | |
| 87 | + ss.stringsToFree = append(ss.stringsToFree, cstr) | |
| 88 | + return cstr | |
| 89 | +} | |
| 90 | + | |
| 91 | +func (ss *saslSession) Close() { | |
| 92 | + for _, cstr := range ss.stringsToFree { | |
| 93 | + C.free(unsafe.Pointer(cstr)) | |
| 94 | + } | |
| 95 | +} | |
| 96 | + | |
| 97 | +func (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 | + | |
| 134 | +func (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 | +} | |
| @@ -0,0 +1,7 @@ | ||
| 1 | +#include <windows.h> | |
| 2 | + | |
| 3 | +#include "sspi_windows.h" | |
| 4 | + | |
| 5 | +SECURITY_STATUS SEC_ENTRY sspi_acquire_credentials_handle(CredHandle* cred_handle, char* username, char* password, char* domain); | |
| 6 | +int sspi_step(CredHandle* cred_handle, int has_context, CtxtHandle* context, PVOID* buffer, ULONG* buffer_length, char* target); | |
| 7 | +int sspi_send_client_authz_id(CtxtHandle* context, PVOID* buffer, ULONG* buffer_length, char* user_plus_realm); | |
| @@ -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 | + | |
| 13 | +static HINSTANCE sspi_secur32_dll = NULL; | |
| 14 | + | |
| 15 | +int 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 | + | |
| 24 | +SECURITY_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 | + | |
| 36 | +SECURITY_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 | + | |
| 58 | +SECURITY_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 | + | |
| 81 | +SECURITY_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 | +} | |
| @@ -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 | + | |
| 17 | +int load_secur32_dll(); | |
| 18 | + | |
| 19 | +SECURITY_STATUS SEC_ENTRY call_sspi_encrypt_message(PCtxtHandle phContext, unsigned long fQOP, PSecBufferDesc pMessage, unsigned long MessageSeqNo); | |
| 20 | + | |
| 21 | +typedef DWORD (WINAPI *encryptMessage_fn)(PCtxtHandle phContext, ULONG fQOP, PSecBufferDesc pMessage, ULONG MessageSeqNo); | |
| 22 | + | |
| 23 | +SECURITY_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 | + | |
| 35 | +typedef 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 | + | |
| 41 | +SECURITY_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 | + | |
| 56 | +typedef 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 | + | |
| 61 | +SECURITY_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 | + | |
| 67 | +typedef DWORD (WINAPI *queryContextAttributes_fn)( | |
| 68 | + PCtxtHandle phContext, unsigned long ulAttribute, void *pBuffer); | |
| 69 | + | |
| 70 | +#endif // SSPI_WINDOWS_H | |
| @@ -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 | +// | |
| 31 | +package scram | |
| 32 | + | |
| 33 | +import ( | |
| 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 | +// | |
| 59 | +type 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 | +// | |
| 80 | +func 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. | |
| 92 | +func (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. | |
| 100 | +func (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. | |
| 106 | +func (c *Client) SetNonce(nonce []byte) { | |
| 107 | + c.clientNonce = nonce | |
| 108 | +} | |
| 109 | + | |
| 110 | +var 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. | |
| 116 | +func (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 | + | |
| 133 | +func (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 | + | |
| 153 | +var b64 = base64.StdEncoding | |
| 154 | + | |
| 155 | +func (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 | + | |
| 200 | +func (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 | + | |
| 218 | +func (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 | + | |
| 236 | +func (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 | + | |
| 254 | +func (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 | +} | |
| @@ -0,0 +1,67 @@ | ||
| 1 | +package scram_test | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "crypto/sha1" | |
| 5 | + "testing" | |
| 6 | + | |
| 7 | + . "gopkg.in/check.v1" | |
| 8 | + "gopkg.in/mgo.v2/internal/scram" | |
| 9 | + "strings" | |
| 10 | +) | |
| 11 | + | |
| 12 | +var _ = Suite(&S{}) | |
| 13 | + | |
| 14 | +func Test(t *testing.T) { TestingT(t) } | |
| 15 | + | |
| 16 | +type S struct{} | |
| 17 | + | |
| 18 | +var 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 | + | |
| 34 | +func (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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +import ( | |
| 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. | |
| 40 | +type log_Logger interface { | |
| 41 | + Output(calldepth int, s string) error | |
| 42 | +} | |
| 43 | + | |
| 44 | +var ( | |
| 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. | |
| 57 | +func 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. | |
| 67 | +func SetDebug(debug bool) { | |
| 68 | + if raceDetector { | |
| 69 | + globalMutex.Lock() | |
| 70 | + defer globalMutex.Unlock() | |
| 71 | + } | |
| 72 | + globalDebug = debug | |
| 73 | +} | |
| 74 | + | |
| 75 | +func 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 | + | |
| 85 | +func 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 | + | |
| 95 | +func 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 | + | |
| 105 | +func 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 | + | |
| 115 | +func 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 | + | |
| 125 | +func 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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +type queue struct { | |
| 30 | + elems []interface{} | |
| 31 | + nelems, popi, pushi int | |
| 32 | +} | |
| 33 | + | |
| 34 | +func (q *queue) Len() int { | |
| 35 | + return q.nelems | |
| 36 | +} | |
| 37 | + | |
| 38 | +func (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 | + | |
| 51 | +func (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 | + | |
| 66 | +func (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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +import ( | |
| 30 | + . "gopkg.in/check.v1" | |
| 31 | +) | |
| 32 | + | |
| 33 | +type QS struct{} | |
| 34 | + | |
| 35 | +var _ = Suite(&QS{}) | |
| 36 | + | |
| 37 | +func (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 | + | |
| 48 | +var 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 | + | |
| 64 | +func (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 | +} | |
| @@ -0,0 +1,5 @@ | ||
| 1 | +// +build !race | |
| 2 | + | |
| 3 | +package mgo | |
| 4 | + | |
| 5 | +const raceDetector = false | |
| @@ -0,0 +1,5 @@ | ||
| 1 | +// +build race | |
| 2 | + | |
| 3 | +package mgo | |
| 4 | + | |
| 5 | +const raceDetector = true | |
| @@ -0,0 +1,11 @@ | ||
| 1 | +//+build sasl | |
| 2 | + | |
| 3 | +package mgo | |
| 4 | + | |
| 5 | +import ( | |
| 6 | + "gopkg.in/mgo.v2/internal/sasl" | |
| 7 | +) | |
| 8 | + | |
| 9 | +func saslNew(cred Credential, host string) (saslStepper, error) { | |
| 10 | + return sasl.New(cred.Username, cred.Password, cred.Mechanism, cred.Service, host) | |
| 11 | +} | |
| @@ -0,0 +1,11 @@ | ||
| 1 | +//+build !sasl | |
| 2 | + | |
| 3 | +package mgo | |
| 4 | + | |
| 5 | +import ( | |
| 6 | + "fmt" | |
| 7 | +) | |
| 8 | + | |
| 9 | +func saslNew(cred Credential, host string) (saslStepper, error) { | |
| 10 | + return nil, fmt.Errorf("SASL support not enabled during build (-tags sasl)") | |
| 11 | +} | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 42 | +type 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 | + | |
| 60 | +type dialer struct { | |
| 61 | + old func(addr net.Addr) (net.Conn, error) | |
| 62 | + new func(addr *ServerAddr) (net.Conn, error) | |
| 63 | +} | |
| 64 | + | |
| 65 | +func (dial dialer) isSet() bool { | |
| 66 | + return dial.old != nil || dial.new != nil | |
| 67 | +} | |
| 68 | + | |
| 69 | +type mongoServerInfo struct { | |
| 70 | + Master bool | |
| 71 | + Mongos bool | |
| 72 | + Tags bson.D | |
| 73 | + MaxWireVersion int | |
| 74 | + SetName string | |
| 75 | +} | |
| 76 | + | |
| 77 | +var defaultServerInfo mongoServerInfo | |
| 78 | + | |
| 79 | +func 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 | + | |
| 94 | +var errPoolLimit = errors.New("per-server connection limit reached") | |
| 95 | +var 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. | |
| 105 | +func (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(). | |
| 152 | +func (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. | |
| 185 | +func (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. | |
| 204 | +func (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 | + | |
| 212 | +func 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. | |
| 227 | +func (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 | + | |
| 244 | +func (server *mongoServer) SetInfo(info *mongoServerInfo) { | |
| 245 | + server.Lock() | |
| 246 | + server.info = info | |
| 247 | + server.Unlock() | |
| 248 | +} | |
| 249 | + | |
| 250 | +func (server *mongoServer) Info() *mongoServerInfo { | |
| 251 | + server.Lock() | |
| 252 | + info := server.info | |
| 253 | + server.Unlock() | |
| 254 | + return info | |
| 255 | +} | |
| 256 | + | |
| 257 | +func (server *mongoServer) hasTags(serverTags []bson.D) bool { | |
| 258 | +NextTagSet: | |
| 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 | + | |
| 277 | +var pingDelay = 5 * time.Second | |
| 278 | + | |
| 279 | +func (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 | + | |
| 332 | +type mongoServerSlice []*mongoServer | |
| 333 | + | |
| 334 | +func (s mongoServerSlice) Len() int { | |
| 335 | + return len(s) | |
| 336 | +} | |
| 337 | + | |
| 338 | +func (s mongoServerSlice) Less(i, j int) bool { | |
| 339 | + return s[i].ResolvedAddr < s[j].ResolvedAddr | |
| 340 | +} | |
| 341 | + | |
| 342 | +func (s mongoServerSlice) Swap(i, j int) { | |
| 343 | + s[i], s[j] = s[j], s[i] | |
| 344 | +} | |
| 345 | + | |
| 346 | +func (s mongoServerSlice) Sort() { | |
| 347 | + sort.Sort(s) | |
| 348 | +} | |
| 349 | + | |
| 350 | +func (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 | + | |
| 358 | +type mongoServers struct { | |
| 359 | + slice mongoServerSlice | |
| 360 | +} | |
| 361 | + | |
| 362 | +func (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 | + | |
| 369 | +func (servers *mongoServers) Add(server *mongoServer) { | |
| 370 | + servers.slice = append(servers.slice, server) | |
| 371 | + servers.slice.Sort() | |
| 372 | +} | |
| 373 | + | |
| 374 | +func (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 | + | |
| 385 | +func (servers *mongoServers) Slice() []*mongoServer { | |
| 386 | + return ([]*mongoServer)(servers.slice) | |
| 387 | +} | |
| 388 | + | |
| 389 | +func (servers *mongoServers) Get(i int) *mongoServer { | |
| 390 | + return servers.slice[i] | |
| 391 | +} | |
| 392 | + | |
| 393 | +func (servers *mongoServers) Len() int { | |
| 394 | + return len(servers.slice) | |
| 395 | +} | |
| 396 | + | |
| 397 | +func (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. | |
| 403 | +func (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 | + | |
| 443 | +func absDuration(d time.Duration) time.Duration { | |
| 444 | + if d < 0 { | |
| 445 | + return -d | |
| 446 | + } | |
| 447 | + return d | |
| 448 | +} | |
| @@ -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 | + | |
| 27 | +package mgo | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 47 | +type mode int | |
| 48 | + | |
| 49 | +const ( | |
| 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 | + | |
| 58 | +type 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 | + | |
| 76 | +type Database struct { | |
| 77 | + Session *Session | |
| 78 | + Name string | |
| 79 | +} | |
| 80 | + | |
| 81 | +type Collection struct { | |
| 82 | + Database *Database | |
| 83 | + Name string // "collection" | |
| 84 | + FullName string // "db.collection" | |
| 85 | +} | |
| 86 | + | |
| 87 | +type Query struct { | |
| 88 | + m sync.Mutex | |
| 89 | + session *Session | |
| 90 | + query // Enables default settings in session. | |
| 91 | +} | |
| 92 | + | |
| 93 | +type query struct { | |
| 94 | + op queryOp | |
| 95 | + prefetch float64 | |
| 96 | + limit int32 | |
| 97 | +} | |
| 98 | + | |
| 99 | +type 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 | + | |
| 107 | +type 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 | + | |
| 123 | +var ( | |
| 124 | + ErrNotFound = errors.New("not found") | |
| 125 | + ErrCursor = errors.New("invalid cursor") | |
| 126 | +) | |
| 127 | + | |
| 128 | +const 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 | +// | |
| 205 | +func 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. | |
| 220 | +func 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. | |
| 233 | +func 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. | |
| 289 | +type 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. | |
| 359 | +type ServerAddr struct { | |
| 360 | + str string | |
| 361 | + tcp *net.TCPAddr | |
| 362 | +} | |
| 363 | + | |
| 364 | +// String returns the address that was provided for the server before resolution. | |
| 365 | +func (addr *ServerAddr) String() string { | |
| 366 | + return addr.str | |
| 367 | +} | |
| 368 | + | |
| 369 | +// TCPAddr returns the resolved TCP address for the server. | |
| 370 | +func (addr *ServerAddr) TCPAddr() *net.TCPAddr { | |
| 371 | + return addr.tcp | |
| 372 | +} | |
| 373 | + | |
| 374 | +// DialWithInfo establishes a new session to the cluster identified by info. | |
| 375 | +func 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 | + | |
| 431 | +func isOptSep(c rune) bool { | |
| 432 | + return c == ';' || c == '&' | |
| 433 | +} | |
| 434 | + | |
| 435 | +type urlInfo struct { | |
| 436 | + addrs []string | |
| 437 | + user string | |
| 438 | + pass string | |
| 439 | + db string | |
| 440 | + options map[string]string | |
| 441 | +} | |
| 442 | + | |
| 443 | +func 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 | + | |
| 484 | +func 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 | + | |
| 499 | +func 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. | |
| 525 | +func (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. | |
| 539 | +func (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. | |
| 550 | +func (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. | |
| 555 | +func (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. | |
| 562 | +func (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 | +// | |
| 584 | +func (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 | +// | |
| 608 | +func (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. | |
| 620 | +type 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. | |
| 649 | +func (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. | |
| 657 | +func (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 | + | |
| 683 | +func (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. | |
| 693 | +func (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. | |
| 718 | +func (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 | +// | |
| 739 | +type 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 | + | |
| 774 | +type Role string | |
| 775 | + | |
| 776 | +const ( | |
| 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 | +// | |
| 805 | +func (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 | + | |
| 868 | +func 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 | + | |
| 873 | +func isNotFound(err error) bool { | |
| 874 | + e, ok := err.(*QueryError) | |
| 875 | + return ok && e.Code == 11 | |
| 876 | +} | |
| 877 | + | |
| 878 | +func isAuthError(err error) bool { | |
| 879 | + e, ok := err.(*QueryError) | |
| 880 | + return ok && e.Code == 13 | |
| 881 | +} | |
| 882 | + | |
| 883 | +func (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. | |
| 913 | +func (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. | |
| 947 | +func (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 | + | |
| 959 | +type 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 | + | |
| 974 | +type 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 | + | |
| 1004 | +type indexKeyInfo struct { | |
| 1005 | + name string | |
| 1006 | + key bson.D | |
| 1007 | + weights bson.D | |
| 1008 | +} | |
| 1009 | + | |
| 1010 | +func 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. | |
| 1088 | +func (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 | +// | |
| 1164 | +func (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 | + | |
| 1194 | +NextField: | |
| 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. | |
| 1234 | +func (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. | |
| 1279 | +func (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 | + | |
| 1331 | +func 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 | + | |
| 1343 | +type indexSlice []Index | |
| 1344 | + | |
| 1345 | +func (idxs indexSlice) Len() int { return len(idxs) } | |
| 1346 | +func (idxs indexSlice) Less(i, j int) bool { return idxs[i].Name < idxs[j].Name } | |
| 1347 | +func (idxs indexSlice) Swap(i, j int) { idxs[i], idxs[j] = idxs[j], idxs[i] } | |
| 1348 | + | |
| 1349 | +func 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. | |
| 1376 | +func (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 | +// | |
| 1392 | +func (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. | |
| 1402 | +func (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. | |
| 1416 | +func (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. | |
| 1425 | +func (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 | + | |
| 1436 | +func (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. | |
| 1445 | +func (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). | |
| 1492 | +func (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. | |
| 1508 | +func (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. | |
| 1519 | +func (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. | |
| 1527 | +func (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. | |
| 1542 | +func (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. | |
| 1561 | +func (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. | |
| 1574 | +func (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. | |
| 1597 | +func (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. | |
| 1604 | +type 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. | |
| 1613 | +func (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 | +// | |
| 1691 | +func (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 | +// | |
| 1725 | +func (s *Session) EnsureSafe(safe *Safe) { | |
| 1726 | + s.m.Lock() | |
| 1727 | + s.ensureSafe(safe) | |
| 1728 | + s.m.Unlock() | |
| 1729 | +} | |
| 1730 | + | |
| 1731 | +func (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 | +// | |
| 1794 | +func (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 | +// | |
| 1816 | +func (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. | |
| 1823 | +func (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. | |
| 1830 | +func (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 | +// | |
| 1859 | +func (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. | |
| 1864 | +func (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 | +// | |
| 1890 | +func (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 | + | |
| 1900 | +type repairCmd struct { | |
| 1901 | + RepairCursor string `bson:"repairCursor"` | |
| 1902 | + Cursor *repairCmdCursor ",omitempty" | |
| 1903 | +} | |
| 1904 | + | |
| 1905 | +type 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. | |
| 1915 | +func (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. | |
| 1948 | +func (c *Collection) FindId(id interface{}) *Query { | |
| 1949 | + return c.Find(bson.D{{"_id", id}}) | |
| 1950 | +} | |
| 1951 | + | |
| 1952 | +type Pipe struct { | |
| 1953 | + session *Session | |
| 1954 | + collection *Collection | |
| 1955 | + pipeline interface{} | |
| 1956 | + allowDisk bool | |
| 1957 | + batchSize int | |
| 1958 | +} | |
| 1959 | + | |
| 1960 | +type pipeCmd struct { | |
| 1961 | + Aggregate string | |
| 1962 | + Pipeline interface{} | |
| 1963 | + Cursor *pipeCmdCursor ",omitempty" | |
| 1964 | + Explain bool ",omitempty" | |
| 1965 | + AllowDisk bool "allowDiskUse,omitempty" | |
| 1966 | +} | |
| 1967 | + | |
| 1968 | +type 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 | +// | |
| 1986 | +func (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. | |
| 2001 | +func (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 | +// | |
| 2060 | +func (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. | |
| 2105 | +func (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. | |
| 2112 | +func (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 | +// | |
| 2136 | +func (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. | |
| 2149 | +func (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. | |
| 2159 | +func (p *Pipe) Batch(n int) *Pipe { | |
| 2160 | + p.batchSize = n | |
| 2161 | + return p | |
| 2162 | +} | |
| 2163 | + | |
| 2164 | +type 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 | + | |
| 2173 | +func (err *LastError) Error() string { | |
| 2174 | + return err.Err | |
| 2175 | +} | |
| 2176 | + | |
| 2177 | +type 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 | + | |
| 2186 | +type QueryError struct { | |
| 2187 | + Code int | |
| 2188 | + Message string | |
| 2189 | + Assertion bool | |
| 2190 | +} | |
| 2191 | + | |
| 2192 | +func (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. | |
| 2199 | +func 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. | |
| 2215 | +func (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 | +// | |
| 2231 | +func (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. | |
| 2244 | +func (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. | |
| 2249 | +type 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 | +// | |
| 2267 | +func (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 | +// | |
| 2288 | +func (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. | |
| 2306 | +func (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 | +// | |
| 2320 | +func (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. | |
| 2333 | +func (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 | +// | |
| 2346 | +func (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. | |
| 2355 | +func (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. | |
| 2360 | +func (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 | +// | |
| 2371 | +type 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 | +// | |
| 2401 | +func (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. | |
| 2430 | +func (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. | |
| 2452 | +func (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. | |
| 2462 | +func (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. | |
| 2472 | +func (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 | +// | |
| 2501 | +func (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 | +// | |
| 2523 | +func (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 | +// | |
| 2577 | +func (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 | +// | |
| 2609 | +func (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. | |
| 2626 | +func (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 | +// | |
| 2664 | +func (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 | +// | |
| 2695 | +func (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 | +// | |
| 2711 | +func (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. | |
| 2723 | +func (q *Query) LogReplay() *Query { | |
| 2724 | + q.m.Lock() | |
| 2725 | + q.op.flags |= flagLogReplay | |
| 2726 | + q.m.Unlock() | |
| 2727 | + return q | |
| 2728 | +} | |
| 2729 | + | |
| 2730 | +func 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 | + | |
| 2748 | +Error: | |
| 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 | +// | |
| 2780 | +func (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. | |
| 2817 | +func (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 | +// | |
| 2868 | +type 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 | +// | |
| 2886 | +func (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 | +// | |
| 2906 | +func (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. | |
| 2915 | +func (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 | + | |
| 2979 | +type 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. | |
| 2987 | +func (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). | |
| 3006 | +func (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 | +// | |
| 3092 | +func (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 | + | |
| 3126 | +func (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. | |
| 3141 | +func (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. | |
| 3166 | +func (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. | |
| 3198 | +func (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 | +// | |
| 3226 | +func (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 | +// | |
| 3331 | +func (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. | |
| 3360 | +func (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. | |
| 3366 | +func (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. | |
| 3372 | +func (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. | |
| 3405 | +func (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 | + | |
| 3432 | +func (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 | + | |
| 3459 | +type 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. | |
| 3467 | +func (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. | |
| 3491 | +func (c *Collection) Count() (n int, err error) { | |
| 3492 | + return c.Find(nil).Count() | |
| 3493 | +} | |
| 3494 | + | |
| 3495 | +type 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 | +// | |
| 3512 | +func (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 | + | |
| 3534 | +type 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 | + | |
| 3547 | +type 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 | + | |
| 3557 | +type 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 | + | |
| 3566 | +type 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 | + | |
| 3576 | +type 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 | +// | |
| 3641 | +func (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 | +// | |
| 3722 | +func 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. | |
| 3747 | +type 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 | + | |
| 3754 | +type findModifyCmd struct { | |
| 3755 | + Collection string "findAndModify" | |
| 3756 | + Query, Update, Sort, Fields interface{} ",omitempty" | |
| 3757 | + Upsert, Remove, New bool ",omitempty" | |
| 3758 | +} | |
| 3759 | + | |
| 3760 | +type 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 | +// | |
| 3792 | +func (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. | |
| 3855 | +type 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. | |
| 3869 | +func (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. | |
| 3883 | +func (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 | + | |
| 3908 | +func (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. | |
| 3970 | +func (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. | |
| 3986 | +func (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 | + | |
| 3997 | +func (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 | + | |
| 4036 | +type 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. | |
| 4061 | +func (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 | + | |
| 4142 | +func (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 | + | |
| 4216 | +func 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 | +} | |
| @@ -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 | + | |
| 27 | +package mgo_test | |
| 28 | + | |
| 29 | +import ( | |
| 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 | + | |
| 45 | +func (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 | + | |
| 56 | +func (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 | + | |
| 67 | +func (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 | + | |
| 87 | +func (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 | + | |
| 98 | +func (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 | + | |
| 109 | +func (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 | + | |
| 123 | +func (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 | + | |
| 142 | +func (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 | + | |
| 152 | +func (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 | + | |
| 167 | +func (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 | + | |
| 213 | +func (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 | + | |
| 259 | +func (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 | + | |
| 297 | +func (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 | + | |
| 313 | +func (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 | + | |
| 344 | +func (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 | + | |
| 376 | +func (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 | + | |
| 404 | +func (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 | + | |
| 434 | +func (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 | + | |
| 487 | +func (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 | + | |
| 524 | +func (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 | + | |
| 567 | +func (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 | + | |
| 596 | +func (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 | + | |
| 614 | +func (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 | + | |
| 634 | +func (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 | + | |
| 675 | +func (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 | + | |
| 707 | +func (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 | + | |
| 731 | +func (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 | + | |
| 757 | +func (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 | + | |
| 777 | +func (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 | + | |
| 799 | +func (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 | + | |
| 813 | +func (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 | + | |
| 827 | +func (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 | + | |
| 849 | +func (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 | + | |
| 873 | +func (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 | + | |
| 892 | +func (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 | + | |
| 958 | +func (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 | + | |
| 986 | +func (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 | + | |
| 1004 | +func (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 | + | |
| 1022 | +func (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 | + | |
| 1040 | +func (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 | + | |
| 1062 | +func (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 | + | |
| 1097 | +func (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 | + | |
| 1116 | +func (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 | + | |
| 1139 | +func (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 | + | |
| 1162 | +func (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 | + | |
| 1194 | +func (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 | + | |
| 1208 | +func (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 | + | |
| 1224 | +func (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 | + | |
| 1242 | +func (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 | + | |
| 1283 | +func (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 | + | |
| 1308 | +func (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 | + | |
| 1325 | +func (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 | + | |
| 1364 | +var cursorTimeout = flag.Bool("cursor-timeout", false, "Enable cursor timeout test") | |
| 1365 | + | |
| 1366 | +func (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 | + | |
| 1408 | +func (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 | + | |
| 1444 | +func 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 | + | |
| 1458 | +func (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 | + | |
| 1521 | +func (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 | + | |
| 1566 | +func (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. | |
| 1619 | +func (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. | |
| 1721 | +func (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. | |
| 1818 | +func (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 | + | |
| 1921 | +func (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 | + | |
| 1991 | +func (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 | + | |
| 2033 | +func (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 | + | |
| 2074 | +func (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 | + | |
| 2101 | +func (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 | + | |
| 2171 | +func (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 | + | |
| 2215 | +func (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 | + | |
| 2250 | +func (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 | + | |
| 2266 | +func (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 | + | |
| 2318 | +func (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 | + | |
| 2396 | +func (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 | + | |
| 2489 | +func (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 | + | |
| 2529 | +func (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 | + | |
| 2546 | +func (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 | + | |
| 2571 | +func (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 | + | |
| 2602 | +var 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 | + | |
| 2714 | +func (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 | + | |
| 2749 | +func (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 | + | |
| 2763 | +func (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 | + | |
| 2788 | +func (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 | + | |
| 2828 | +func (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 | + | |
| 2863 | +func (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 | + | |
| 2904 | +func (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 | + | |
| 2941 | +func (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 | + | |
| 2976 | +var testTTL = flag.Bool("test-ttl", false, "test TTL collections (may take 1 minute)") | |
| 2977 | + | |
| 2978 | +func (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 | + | |
| 3026 | +func (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 | + | |
| 3044 | +func (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 | + | |
| 3079 | +func (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 | + | |
| 3110 | +func (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.
This diff is large: its 678 lines aren’t shown here.
This diff is large: its 148 lines aren’t shown here.
This diff is large: its 255 lines aren’t shown here.
This diff is large: its 16 lines aren’t shown here.
This diff is large: its 12 lines aren’t shown here.
This diff is large: its 13 lines aren’t shown here.