Unified · Split
| @@ -34,7 +34,8 @@ lint: · 33 unmodified lines | |||
| 34 | 34 | ||
| 35 | cyclo: | 35 | cyclo: |
| 36 | @echo "Running $@:" | 36 | @echo "Running $@:" |
| 37 | @GO15VENDOREXPERIMENT=1 gocyclo -over 25 . | 37 | @GO15VENDOREXPERIMENT=1 gocyclo -over 25 *.go |
| 38 | @GO15VENDOREXPERIMENT=1 gocyclo -over 25 pkg | ||
| 38 | 39 | ||
| 39 | build: getdeps verifiers | 40 | build: getdeps verifiers |
| 40 | @echo "Installing minio:" | 41 | @echo "Installing minio:" |
| @@ -42,6 +43,7 @@ build: getdeps verifiers · 1 unmodified line | |||
| 42 | 43 | ||
| 43 | test: build | 44 | test: build |
| 44 | @echo "Running all testing:" | 45 | @echo "Running all testing:" |
| 46 | @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) . | ||
| 45 | @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg... | 47 | @GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg... |
| 46 | 48 | ||
| 47 | gomake-all: build | 49 | gomake-all: build |
| @@ -21,7 +21,7 @@ _init() { · 20 unmodified lines | |||
| 21 | 21 | ||
| 22 | ## Minimum required versions for build dependencies | 22 | ## Minimum required versions for build dependencies |
| 23 | GCC_VERSION="4.0" | 23 | GCC_VERSION="4.0" |
| 24 | CLANG_VERSION="3.5" | 24 | LLVM_VERSION="7.0.0" |
| 25 | YASM_VERSION="1.2.0" | 25 | YASM_VERSION="1.2.0" |
| 26 | GIT_VERSION="1.0" | 26 | GIT_VERSION="1.0" |
| 27 | GO_VERSION="1.5.1" | 27 | GO_VERSION="1.5.1" |
| @@ -173,7 +173,7 @@ is_supported_arch() { · 145 unmodified lines | |||
| 173 | check_deps() { | 173 | check_deps() { |
| 174 | check_version "$(env go version 2>/dev/null | sed 's/^.* go\([0-9.]*\).*$/\1/')" "${GO_VERSION}" | 174 | check_version "$(env go version 2>/dev/null | sed 's/^.* go\([0-9.]*\).*$/\1/')" "${GO_VERSION}" |
| 175 | if [ $? -ge 2 ]; then | 175 | if [ $? -ge 2 ]; then |
| 176 | MISSING="${MISSING} golang(1.5)" | 176 | MISSING="${MISSING} golang(${GO_VERSION})" |
| 177 | fi | 177 | fi |
| 178 | 178 | ||
| 179 | check_version "$(env git --version 2>/dev/null | sed -e 's/^.* \([0-9.\].*\).*$/\1/' -e 's/^\([0-9.\]*\).*/\1/g')" "${GIT_VERSION}" | 179 | check_version "$(env git --version 2>/dev/null | sed -e 's/^.* \([0-9.\].*\).*$/\1/' -e 's/^\([0-9.\]*\).*/\1/g')" "${GIT_VERSION}" |
| @@ -185,13 +185,13 @@ check_deps() { · 5 unmodified lines | |||
| 185 | "Linux") | 185 | "Linux") |
| 186 | check_version "$(env gcc --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${GCC_VERSION}" | 186 | check_version "$(env gcc --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${GCC_VERSION}" |
| 187 | if [ $? -ge 2 ]; then | 187 | if [ $? -ge 2 ]; then |
| 188 | MISSING="${MISSING} build-essential" | 188 | MISSING="${MISSING} build-essential(${GCC_VERSION})" |
| 189 | fi | 189 | fi |
| 190 | ;; | 190 | ;; |
| 191 | "Darwin") | 191 | "Darwin") |
| 192 | check_version "$(env gcc --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${CLANG_VERSION}" | 192 | check_version "$(env gcc --version 2>/dev/null | awk '{print $4}' | head -1)" "${LLVM_VERSION}" |
| 193 | if [ $? -ge 2 ]; then | 193 | if [ $? -ge 2 ]; then |
| 194 | MISSING="${MISSING} xcode-cli" | 194 | MISSING="${MISSING} xcode-cli(${LLVM_VERSION})" |
| 195 | fi | 195 | fi |
| 196 | ;; | 196 | ;; |
| 197 | "*") | 197 | "*") |
| @@ -200,7 +200,7 @@ check_deps() { · 2 unmodified lines | |||
| 200 | 200 | ||
| 201 | check_version "$(env yasm --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${YASM_VERSION}" | 201 | check_version "$(env yasm --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${YASM_VERSION}" |
| 202 | if [ $? -ge 2 ]; then | 202 | if [ $? -ge 2 ]; then |
| 203 | MISSING="${MISSING} yasm(1.2.0)" | 203 | MISSING="${MISSING} yasm(${YASM_VERSION})" |
| 204 | fi | 204 | fi |
| 205 | 205 | ||
| 206 | } | 206 | } |
| @@ -16,10 +16,7 @@ · 15 unmodified lines | |||
| 16 | 16 | ||
| 17 | package main | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import "github.com/minio/cli" |
| 20 | "github.com/minio/cli" | ||
| 21 | "github.com/minio/minio/pkg/controller" | ||
| 22 | ) | ||
| 23 | 20 | ||
| 24 | var controllerCmd = cli.Command{ | 21 | var controllerCmd = cli.Command{ |
| 25 | Name: "controller", | 22 | Name: "controller", |
| @@ -43,6 +40,6 @@ func controllerMain(c *cli.Context) { · 17 unmodified lines | |||
| 43 | cli.ShowCommandHelpAndExit(c, "controller", 1) | 40 | cli.ShowCommandHelpAndExit(c, "controller", 1) |
| 44 | } | 41 | } |
| 45 | 42 | ||
| 46 | err := controller.Start() | 43 | err := StartController() |
| 47 | errorIf(err.Trace(), "Failed to start minio controller.", nil) | 44 | errorIf(err.Trace(), "Failed to start minio controller.", nil) |
| 48 | } | 45 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package controller | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| @@ -22,17 +22,16 @@ import ( · 1 unmodified line | |||
| 22 | router "github.com/gorilla/mux" | 22 | router "github.com/gorilla/mux" |
| 23 | jsonrpc "github.com/gorilla/rpc/v2" | 23 | jsonrpc "github.com/gorilla/rpc/v2" |
| 24 | "github.com/gorilla/rpc/v2/json" | 24 | "github.com/gorilla/rpc/v2/json" |
| 25 | "github.com/minio/minio/pkg/controller/rpc" | ||
| 26 | ) | 25 | ) |
| 27 | 26 | ||
| 28 | // getRPCHandler rpc handler | 27 | // getRPCHandler rpc handler |
| 29 | func getRPCHandler() http.Handler { | 28 | func getRPCHandler() http.Handler { |
| 30 | s := jsonrpc.NewServer() | 29 | s := jsonrpc.NewServer() |
| 31 | s.RegisterCodec(json.NewCodec(), "application/json") | 30 | s.RegisterCodec(json.NewCodec(), "application/json") |
| 32 | s.RegisterService(new(rpc.VersionService), "Version") | 31 | s.RegisterService(new(VersionService), "Version") |
| 33 | s.RegisterService(new(rpc.DonutService), "Donut") | 32 | s.RegisterService(new(DonutService), "Donut") |
| 34 | s.RegisterService(new(rpc.AuthService), "Auth") | 33 | s.RegisterService(new(AuthService), "Auth") |
| 35 | s.RegisterService(new(rpc.ServerService), "Server") | 34 | s.RegisterService(new(ServerService), "Server") |
| 36 | // Add new RPC services here | 35 | // Add new RPC services here |
| 37 | return registerRPC(router.NewRouter(), s) | 36 | return registerRPC(router.NewRouter(), s) |
| 38 | } | 37 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package rpc | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "errors" | 20 | "errors" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package rpc | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package rpc | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package rpc | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| @@ -38,8 +38,7 @@ type VersionReply struct { · 17 unmodified lines | |||
| 38 | // Get version | 38 | // Get version |
| 39 | func (v *VersionService) Get(r *http.Request, args *VersionArgs, reply *VersionReply) error { | 39 | func (v *VersionService) Get(r *http.Request, args *VersionArgs, reply *VersionReply) error { |
| 40 | reply.Version = "0.0.1" | 40 | reply.Version = "0.0.1" |
| 41 | //TODO: Better approach needed here to pass global states like version. --ab. | 41 | reply.BuildDate = minioVersion |
| 42 | // reply.BuildDate = version.Version | ||
| 43 | reply.Architecture = runtime.GOARCH | 42 | reply.Architecture = runtime.GOARCH |
| 44 | reply.OperatingSystem = runtime.GOOS | 43 | reply.OperatingSystem = runtime.GOOS |
| 45 | return nil | 44 | return nil |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package controller | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "fmt" | 20 | "fmt" |
| @@ -54,8 +54,8 @@ func getRPCServer(rpcHandler http.Handler) (*http.Server, *probe.Error) { · 33 unmodified lines | |||
| 54 | return httpServer, nil | 54 | return httpServer, nil |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | // Start starts a controller | 57 | // StartController starts a minio controller |
| 58 | func Start() *probe.Error { | 58 | func StartController() *probe.Error { |
| 59 | rpcServer, err := getRPCServer(getRPCHandler()) | 59 | rpcServer, err := getRPCServer(getRPCHandler()) |
| 60 | if err != nil { | 60 | if err != nil { |
| 61 | return err.Trace() | 61 | return err.Trace() |
| @@ -14,31 +14,26 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package controller | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "io/ioutil" | 20 | "io/ioutil" |
| 21 | "net/http" | 21 | "net/http" |
| 22 | "net/http/httptest" | 22 | "net/http/httptest" |
| 23 | "os" | 23 | "os" |
| 24 | "testing" | ||
| 25 | 24 | ||
| 26 | jsonrpc "github.com/gorilla/rpc/v2/json" | 25 | "github.com/gorilla/rpc/v2/json" |
| 27 | "github.com/minio/minio/pkg/auth" | 26 | "github.com/minio/minio/pkg/auth" |
| 28 | "github.com/minio/minio/pkg/controller/rpc" | ||
| 29 | . "gopkg.in/check.v1" | 27 | . "gopkg.in/check.v1" |
| 30 | ) | 28 | ) |
| 31 | 29 | ||
| 32 | // Hook up gocheck into the "go test" runner. | 30 | type ControllerRPCSuite struct{} |
| 33 | func Test(t *testing.T) { TestingT(t) } | ||
| 34 | 31 | ||
| 35 | type MySuite struct{} | 32 | var _ = Suite(&ControllerRPCSuite{}) |
| 36 | |||
| 37 | var _ = Suite(&MySuite{}) | ||
| 38 | 33 | ||
| 39 | var testRPCServer *httptest.Server | 34 | var testRPCServer *httptest.Server |
| 40 | 35 | ||
| 41 | func (s *MySuite) SetUpSuite(c *C) { | 36 | func (s *ControllerRPCSuite) SetUpSuite(c *C) { |
| 42 | root, err := ioutil.TempDir(os.TempDir(), "api-") | 37 | root, err := ioutil.TempDir(os.TempDir(), "api-") |
| 43 | c.Assert(err, IsNil) | 38 | c.Assert(err, IsNil) |
| 44 | auth.SetAuthConfigPath(root) | 39 | auth.SetAuthConfigPath(root) |
| @@ -46,136 +41,136 @@ func (s *MySuite) SetUpSuite(c *C) { · 1 unmodified line | |||
| 46 | testRPCServer = httptest.NewServer(getRPCHandler()) | 41 | testRPCServer = httptest.NewServer(getRPCHandler()) |
| 47 | } | 42 | } |
| 48 | 43 | ||
| 49 | func (s *MySuite) TearDownSuite(c *C) { | 44 | func (s *ControllerRPCSuite) TearDownSuite(c *C) { |
| 50 | testRPCServer.Close() | 45 | testRPCServer.Close() |
| 51 | } | 46 | } |
| 52 | 47 | ||
| 53 | func (s *MySuite) TestMemStats(c *C) { | 48 | func (s *ControllerRPCSuite) TestMemStats(c *C) { |
| 54 | op := rpc.Operation{ | 49 | op := rpcOperation{ |
| 55 | Method: "Server.MemStats", | 50 | Method: "Server.MemStats", |
| 56 | Request: rpc.ServerArgs{}, | 51 | Request: ServerArgs{}, |
| 57 | } | 52 | } |
| 58 | req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 53 | req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 59 | c.Assert(err, IsNil) | 54 | c.Assert(err, IsNil) |
| 60 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 55 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 61 | resp, err := req.Do() | 56 | resp, err := req.Do() |
| 62 | c.Assert(err, IsNil) | 57 | c.Assert(err, IsNil) |
| 63 | c.Assert(resp.StatusCode, Equals, http.StatusOK) | 58 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 64 | 59 | ||
| 65 | var reply rpc.MemStatsReply | 60 | var reply MemStatsReply |
| 66 | c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | 61 | c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) |
| 67 | resp.Body.Close() | 62 | resp.Body.Close() |
| 68 | c.Assert(reply, Not(DeepEquals), rpc.MemStatsReply{}) | 63 | c.Assert(reply, Not(DeepEquals), MemStatsReply{}) |
| 69 | } | 64 | } |
| 70 | 65 | ||
| 71 | func (s *MySuite) TestSysInfo(c *C) { | 66 | func (s *ControllerRPCSuite) TestSysInfo(c *C) { |
| 72 | op := rpc.Operation{ | 67 | op := rpcOperation{ |
| 73 | Method: "Server.SysInfo", | 68 | Method: "Server.SysInfo", |
| 74 | Request: rpc.ServerArgs{}, | 69 | Request: ServerArgs{}, |
| 75 | } | 70 | } |
| 76 | req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 71 | req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 77 | c.Assert(err, IsNil) | 72 | c.Assert(err, IsNil) |
| 78 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 73 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 79 | resp, err := req.Do() | 74 | resp, err := req.Do() |
| 80 | c.Assert(err, IsNil) | 75 | c.Assert(err, IsNil) |
| 81 | c.Assert(resp.StatusCode, Equals, http.StatusOK) | 76 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 82 | 77 | ||
| 83 | var reply rpc.SysInfoReply | 78 | var reply SysInfoReply |
| 84 | c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | 79 | c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) |
| 85 | resp.Body.Close() | 80 | resp.Body.Close() |
| 86 | c.Assert(reply, Not(DeepEquals), rpc.SysInfoReply{}) | 81 | c.Assert(reply, Not(DeepEquals), SysInfoReply{}) |
| 87 | } | 82 | } |
| 88 | 83 | ||
| 89 | func (s *MySuite) TestServerList(c *C) { | 84 | func (s *ControllerRPCSuite) TestServerList(c *C) { |
| 90 | op := rpc.Operation{ | 85 | op := rpcOperation{ |
| 91 | Method: "Server.List", | 86 | Method: "Server.List", |
| 92 | Request: rpc.ServerArgs{}, | 87 | Request: ServerArgs{}, |
| 93 | } | 88 | } |
| 94 | req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 89 | req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 95 | c.Assert(err, IsNil) | 90 | c.Assert(err, IsNil) |
| 96 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 91 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 97 | resp, err := req.Do() | 92 | resp, err := req.Do() |
| 98 | c.Assert(err, IsNil) | 93 | c.Assert(err, IsNil) |
| 99 | c.Assert(resp.StatusCode, Equals, http.StatusOK) | 94 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 100 | 95 | ||
| 101 | var reply rpc.ServerListReply | 96 | var reply ServerListReply |
| 102 | c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | 97 | c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) |
| 103 | resp.Body.Close() | 98 | resp.Body.Close() |
| 104 | c.Assert(reply, Not(DeepEquals), rpc.ServerListReply{}) | 99 | c.Assert(reply, Not(DeepEquals), ServerListReply{}) |
| 105 | } | 100 | } |
| 106 | 101 | ||
| 107 | func (s *MySuite) TestServerAdd(c *C) { | 102 | func (s *ControllerRPCSuite) TestServerAdd(c *C) { |
| 108 | op := rpc.Operation{ | 103 | op := rpcOperation{ |
| 109 | Method: "Server.Add", | 104 | Method: "Server.Add", |
| 110 | Request: rpc.ServerArgs{MinioServers: []rpc.MinioServer{}}, | 105 | Request: ServerArgs{MinioServers: []MinioServer{}}, |
| 111 | } | 106 | } |
| 112 | req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 107 | req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 113 | c.Assert(err, IsNil) | 108 | c.Assert(err, IsNil) |
| 114 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 109 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 115 | resp, err := req.Do() | 110 | resp, err := req.Do() |
| 116 | c.Assert(err, IsNil) | 111 | c.Assert(err, IsNil) |
| 117 | c.Assert(resp.StatusCode, Equals, http.StatusOK) | 112 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 118 | 113 | ||
| 119 | var reply rpc.ServerAddReply | 114 | var reply ServerAddReply |
| 120 | c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | 115 | c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) |
| 121 | resp.Body.Close() | 116 | resp.Body.Close() |
| 122 | c.Assert(reply, Not(DeepEquals), rpc.ServerAddReply{ServersAdded: []rpc.MinioServer{}}) | 117 | c.Assert(reply, Not(DeepEquals), ServerAddReply{ServersAdded: []MinioServer{}}) |
| 123 | } | 118 | } |
| 124 | 119 | ||
| 125 | func (s *MySuite) TestAuth(c *C) { | 120 | func (s *ControllerRPCSuite) TestAuth(c *C) { |
| 126 | op := rpc.Operation{ | 121 | op := rpcOperation{ |
| 127 | Method: "Auth.Generate", | 122 | Method: "Auth.Generate", |
| 128 | Request: rpc.AuthArgs{User: "newuser"}, | 123 | Request: AuthArgs{User: "newuser"}, |
| 129 | } | 124 | } |
| 130 | req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 125 | req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 131 | c.Assert(err, IsNil) | 126 | c.Assert(err, IsNil) |
| 132 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 127 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 133 | resp, err := req.Do() | 128 | resp, err := req.Do() |
| 134 | c.Assert(err, IsNil) | 129 | c.Assert(err, IsNil) |
| 135 | c.Assert(resp.StatusCode, Equals, http.StatusOK) | 130 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 136 | 131 | ||
| 137 | var reply rpc.AuthReply | 132 | var reply AuthReply |
| 138 | c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil) | 133 | c.Assert(json.DecodeClientResponse(resp.Body, &reply), IsNil) |
| 139 | resp.Body.Close() | 134 | resp.Body.Close() |
| 140 | c.Assert(reply, Not(DeepEquals), rpc.AuthReply{}) | 135 | c.Assert(reply, Not(DeepEquals), AuthReply{}) |
| 141 | c.Assert(len(reply.AccessKeyID), Equals, 20) | 136 | c.Assert(len(reply.AccessKeyID), Equals, 20) |
| 142 | c.Assert(len(reply.SecretAccessKey), Equals, 40) | 137 | c.Assert(len(reply.SecretAccessKey), Equals, 40) |
| 143 | c.Assert(len(reply.Name), Not(Equals), 0) | 138 | c.Assert(len(reply.Name), Not(Equals), 0) |
| 144 | 139 | ||
| 145 | op = rpc.Operation{ | 140 | op = rpcOperation{ |
| 146 | Method: "Auth.Fetch", | 141 | Method: "Auth.Fetch", |
| 147 | Request: rpc.AuthArgs{User: "newuser"}, | 142 | Request: AuthArgs{User: "newuser"}, |
| 148 | } | 143 | } |
| 149 | req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 144 | req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 150 | c.Assert(err, IsNil) | 145 | c.Assert(err, IsNil) |
| 151 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 146 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 152 | resp, err = req.Do() | 147 | resp, err = req.Do() |
| 153 | c.Assert(err, IsNil) | 148 | c.Assert(err, IsNil) |
| 154 | c.Assert(resp.StatusCode, Equals, http.StatusOK) | 149 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 155 | 150 | ||
| 156 | var newReply rpc.AuthReply | 151 | var newReply AuthReply |
| 157 | c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &newReply), IsNil) | 152 | c.Assert(json.DecodeClientResponse(resp.Body, &newReply), IsNil) |
| 158 | resp.Body.Close() | 153 | resp.Body.Close() |
| 159 | c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{}) | 154 | c.Assert(newReply, Not(DeepEquals), AuthReply{}) |
| 160 | c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID) | 155 | c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID) |
| 161 | c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey) | 156 | c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey) |
| 162 | c.Assert(len(reply.Name), Not(Equals), 0) | 157 | c.Assert(len(reply.Name), Not(Equals), 0) |
| 163 | 158 | ||
| 164 | op = rpc.Operation{ | 159 | op = rpcOperation{ |
| 165 | Method: "Auth.Reset", | 160 | Method: "Auth.Reset", |
| 166 | Request: rpc.AuthArgs{User: "newuser"}, | 161 | Request: AuthArgs{User: "newuser"}, |
| 167 | } | 162 | } |
| 168 | req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 163 | req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 169 | c.Assert(err, IsNil) | 164 | c.Assert(err, IsNil) |
| 170 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 165 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 171 | resp, err = req.Do() | 166 | resp, err = req.Do() |
| 172 | c.Assert(err, IsNil) | 167 | c.Assert(err, IsNil) |
| 173 | c.Assert(resp.StatusCode, Equals, http.StatusOK) | 168 | c.Assert(resp.StatusCode, Equals, http.StatusOK) |
| 174 | 169 | ||
| 175 | var resetReply rpc.AuthReply | 170 | var resetReply AuthReply |
| 176 | c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &resetReply), IsNil) | 171 | c.Assert(json.DecodeClientResponse(resp.Body, &resetReply), IsNil) |
| 177 | resp.Body.Close() | 172 | resp.Body.Close() |
| 178 | c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{}) | 173 | c.Assert(newReply, Not(DeepEquals), AuthReply{}) |
| 179 | c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID) | 174 | c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID) |
| 180 | c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey) | 175 | c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey) |
| 181 | c.Assert(len(reply.Name), Not(Equals), 0) | 176 | c.Assert(len(reply.Name), Not(Equals), 0) |
| @@ -183,11 +178,11 @@ func (s *MySuite) TestAuth(c *C) { · 1 unmodified line | |||
| 183 | // these operations should fail | 178 | // these operations should fail |
| 184 | 179 | ||
| 185 | /// generating access for existing user fails | 180 | /// generating access for existing user fails |
| 186 | op = rpc.Operation{ | 181 | op = rpcOperation{ |
| 187 | Method: "Auth.Generate", | 182 | Method: "Auth.Generate", |
| 188 | Request: rpc.AuthArgs{User: "newuser"}, | 183 | Request: AuthArgs{User: "newuser"}, |
| 189 | } | 184 | } |
| 190 | req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 185 | req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 191 | c.Assert(err, IsNil) | 186 | c.Assert(err, IsNil) |
| 192 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 187 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 193 | resp, err = req.Do() | 188 | resp, err = req.Do() |
| @@ -195,11 +190,11 @@ func (s *MySuite) TestAuth(c *C) { · 1 unmodified line | |||
| 195 | c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) | 190 | c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) |
| 196 | 191 | ||
| 197 | /// null user provided invalid | 192 | /// null user provided invalid |
| 198 | op = rpc.Operation{ | 193 | op = rpcOperation{ |
| 199 | Method: "Auth.Generate", | 194 | Method: "Auth.Generate", |
| 200 | Request: rpc.AuthArgs{User: ""}, | 195 | Request: AuthArgs{User: ""}, |
| 201 | } | 196 | } |
| 202 | req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) | 197 | req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) |
| 203 | c.Assert(err, IsNil) | 198 | c.Assert(err, IsNil) |
| 204 | c.Assert(req.Get("Content-Type"), Equals, "application/json") | 199 | c.Assert(req.Get("Content-Type"), Equals, "application/json") |
| 205 | resp, err = req.Do() | 200 | resp, err = req.Do() |
| @@ -27,7 +27,11 @@ import ( · 26 unmodified lines | |||
| 27 | . "gopkg.in/check.v1" | 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 | var buffer bytes.Buffer | 35 | var buffer bytes.Buffer |
| 32 | var fields logrus.Fields | 36 | var fields logrus.Fields |
| 33 | log.Out = &buffer | 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 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -22,14 +22,5 @@ import ( · 16 unmodified lines | |||
| 22 | . "gopkg.in/check.v1" | 22 | . "gopkg.in/check.v1" |
| 23 | ) | 23 | ) |
| 24 | 24 | ||
| 25 | // Hook up gocheck into the "go test" runner. | ||
| 25 | func Test(t *testing.T) { TestingT(t) } | 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 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package rpc | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "bytes" | 20 | "bytes" |
| @@ -24,20 +24,20 @@ import ( · 3 unmodified lines | |||
| 24 | "github.com/minio/minio/pkg/probe" | 24 | "github.com/minio/minio/pkg/probe" |
| 25 | ) | 25 | ) |
| 26 | 26 | ||
| 27 | // Operation RPC operation | 27 | // rpcOperation RPC operation |
| 28 | type Operation struct { | 28 | type rpcOperation struct { |
| 29 | Method string | 29 | Method string |
| 30 | Request interface{} | 30 | Request interface{} |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | // Request rpc client request | 33 | // rpcRequest rpc client request |
| 34 | type Request struct { | 34 | type rpcRequest struct { |
| 35 | req *http.Request | 35 | req *http.Request |
| 36 | transport http.RoundTripper | 36 | transport http.RoundTripper |
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | // NewRequest initiate a new client RPC request | 39 | // newRPCRequest initiate a new client RPC request |
| 40 | func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request, *probe.Error) { | 40 | func newRPCRequest(url string, op rpcOperation, transport http.RoundTripper) (*rpcRequest, *probe.Error) { |
| 41 | params, err := json.EncodeClientRequest(op.Method, op.Request) | 41 | params, err := json.EncodeClientRequest(op.Method, op.Request) |
| 42 | if err != nil { | 42 | if err != nil { |
| 43 | return nil, probe.NewError(err) | 43 | return nil, probe.NewError(err) |
| @@ -46,7 +46,7 @@ func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request · 2 unmodified lines | |||
| 46 | if err != nil { | 46 | if err != nil { |
| 47 | return nil, probe.NewError(err) | 47 | return nil, probe.NewError(err) |
| 48 | } | 48 | } |
| 49 | rpcReq := &Request{} | 49 | rpcReq := &rpcRequest{} |
| 50 | rpcReq.req = req | 50 | rpcReq.req = req |
| 51 | rpcReq.req.Header.Set("Content-Type", "application/json") | 51 | rpcReq.req.Header.Set("Content-Type", "application/json") |
| 52 | if transport == nil { | 52 | if transport == nil { |
| @@ -57,7 +57,7 @@ func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request · 4 unmodified lines | |||
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | // Do - make a http connection | 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 | resp, err := r.transport.RoundTrip(r.req) | 61 | resp, err := r.transport.RoundTrip(r.req) |
| 62 | if err != nil { | 62 | if err != nil { |
| 63 | if err, ok := probe.UnwrapError(err); ok { | 63 | if err, ok := probe.UnwrapError(err); ok { |
| @@ -69,11 +69,11 @@ func (r Request) Do() (*http.Response, *probe.Error) { · 5 unmodified lines | |||
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | // Get - get value of requested header | 71 | // Get - get value of requested header |
| 72 | func (r Request) Get(key string) string { | 72 | func (r rpcRequest) Get(key string) string { |
| 73 | return r.req.Header.Get(key) | 73 | return r.req.Header.Get(key) |
| 74 | } | 74 | } |
| 75 | 75 | ||
| 76 | // Set - set value of a header key | 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 | r.req.Header.Set(key, value) | 78 | r.req.Header.Set(key, value) |
| 79 | } | 79 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import "net/http" | 19 | import "net/http" |
| 20 | 20 | ||
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| @@ -24,7 +24,7 @@ import ( · 3 unmodified lines | |||
| 24 | "github.com/minio/minio/pkg/probe" | 24 | "github.com/minio/minio/pkg/probe" |
| 25 | ) | 25 | ) |
| 26 | 26 | ||
| 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 | vars := mux.Vars(req) | 28 | vars := mux.Vars(req) |
| 29 | bucket := vars["bucket"] | 29 | bucket := vars["bucket"] |
| 30 | 30 | ||
| @@ -67,10 +67,10 @@ func (api Minio) isValidOp(w http.ResponseWriter, req *http.Request, acceptsCont · 36 unmodified lines | |||
| 67 | // using the Initiate Multipart Upload request, but has not yet been completed or aborted. | 67 | // using the Initiate Multipart Upload request, but has not yet been completed or aborted. |
| 68 | // This operation returns at most 1,000 multipart uploads in the response. | 68 | // This operation returns at most 1,000 multipart uploads in the response. |
| 69 | // | 69 | // |
| 70 | func (api Minio) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) { | 70 | func (api MinioAPI) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) { |
| 71 | // Ticket master block | 71 | // Ticket master block |
| 72 | { | 72 | { |
| 73 | op := Operation{} | 73 | op := APIOperation{} |
| 74 | op.ProceedCh = make(chan struct{}) | 74 | op.ProceedCh = make(chan struct{}) |
| 75 | api.OP <- op | 75 | api.OP <- op |
| 76 | // block until ticket master gives us a go | 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 | // of the objects in a bucket. You can use the request parameters as selection | 133 | // of the objects in a bucket. You can use the request parameters as selection |
| 134 | // criteria to return a subset of the objects in a bucket. | 134 | // criteria to return a subset of the objects in a bucket. |
| 135 | // | 135 | // |
| 136 | func (api Minio) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { | 136 | func (api MinioAPI) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { |
| 137 | // Ticket master block | 137 | // Ticket master block |
| 138 | { | 138 | { |
| 139 | op := Operation{} | 139 | op := APIOperation{} |
| 140 | op.ProceedCh = make(chan struct{}) | 140 | op.ProceedCh = make(chan struct{}) |
| 141 | api.OP <- op | 141 | api.OP <- op |
| 142 | // block until Ticket master gives us a go | 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 | // This implementation of the GET operation returns a list of all buckets | 209 | // This implementation of the GET operation returns a list of all buckets |
| 210 | // owned by the authenticated sender of the request. | 210 | // owned by the authenticated sender of the request. |
| 211 | func (api Minio) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { | 211 | func (api MinioAPI) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { |
| 212 | // Ticket master block | 212 | // Ticket master block |
| 213 | { | 213 | { |
| 214 | op := Operation{} | 214 | op := APIOperation{} |
| 215 | op.ProceedCh = make(chan struct{}) | 215 | op.ProceedCh = make(chan struct{}) |
| 216 | api.OP <- op | 216 | api.OP <- op |
| 217 | // block until Ticket master gives us a go | 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 | // PutBucketHandler - PUT Bucket | 260 | // PutBucketHandler - PUT Bucket |
| 261 | // ---------- | 261 | // ---------- |
| 262 | // This implementation of the PUT operation creates a new bucket for authenticated request | 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 | // Ticket master block | 264 | // Ticket master block |
| 265 | { | 265 | { |
| 266 | op := Operation{} | 266 | op := APIOperation{} |
| 267 | op.ProceedCh = make(chan struct{}) | 267 | op.ProceedCh = make(chan struct{}) |
| 268 | api.OP <- op | 268 | api.OP <- op |
| 269 | // block until Ticket master gives us a go | 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 | // PutBucketACLHandler - PUT Bucket ACL | 338 | // PutBucketACLHandler - PUT Bucket ACL |
| 339 | // ---------- | 339 | // ---------- |
| 340 | // This implementation of the PUT operation modifies the bucketACL for authenticated request | 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 | // Ticket master block | 342 | // Ticket master block |
| 343 | { | 343 | { |
| 344 | op := Operation{} | 344 | op := APIOperation{} |
| 345 | op.ProceedCh = make(chan struct{}) | 345 | op.ProceedCh = make(chan struct{}) |
| 346 | api.OP <- op | 346 | api.OP <- op |
| 347 | // block until Ticket master gives us a go | 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 | // The operation returns a 200 OK if the bucket exists and you | 395 | // The operation returns a 200 OK if the bucket exists and you |
| 396 | // have permission to access it. Otherwise, the operation might | 396 | // have permission to access it. Otherwise, the operation might |
| 397 | // return responses such as 404 Not Found and 403 Forbidden. | 397 | // return responses such as 404 Not Found and 403 Forbidden. |
| 398 | func (api Minio) HeadBucketHandler(w http.ResponseWriter, req *http.Request) { | 398 | func (api MinioAPI) HeadBucketHandler(w http.ResponseWriter, req *http.Request) { |
| 399 | // Ticket master block | 399 | // Ticket master block |
| 400 | { | 400 | { |
| 401 | op := Operation{} | 401 | op := APIOperation{} |
| 402 | op.ProceedCh = make(chan struct{}) | 402 | op.ProceedCh = make(chan struct{}) |
| 403 | api.OP <- op | 403 | api.OP <- op |
| 404 | // block until Ticket master gives us a go | 404 | // block until Ticket master gives us a go |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import "net/http" | 19 | import "net/http" |
| 20 | 20 | ||
| @@ -14,12 +14,12 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import "encoding/xml" | 19 | import "encoding/xml" |
| 20 | 20 | ||
| 21 | // Config - http server config | 21 | // APIConfig - http server config |
| 22 | type Config struct { | 22 | type APIConfig struct { |
| 23 | Address string | 23 | Address string |
| 24 | TLS bool | 24 | TLS bool |
| 25 | CertFile string | 25 | CertFile string |
| @@ -14,22 +14,22 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "encoding/xml" | 20 | "encoding/xml" |
| 21 | "net/http" | 21 | "net/http" |
| 22 | ) | 22 | ) |
| 23 | 23 | ||
| 24 | // Error structure | 24 | // APIError structure |
| 25 | type Error struct { | 25 | type APIError struct { |
| 26 | Code string | 26 | Code string |
| 27 | Description string | 27 | Description string |
| 28 | HTTPStatusCode int | 28 | HTTPStatusCode int |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | // ErrorResponse - error response format | 31 | // APIErrorResponse - error response format |
| 32 | type ErrorResponse struct { | 32 | type APIErrorResponse struct { |
| 33 | XMLName xml.Name `xml:"Error" json:"-"` | 33 | XMLName xml.Name `xml:"Error" json:"-"` |
| 34 | Code string | 34 | Code string |
| 35 | Message string | 35 | Message string |
| @@ -77,8 +77,8 @@ const ( · 41 unmodified lines | |||
| 77 | NotAcceptable = iota + 30 | 77 | NotAcceptable = iota + 30 |
| 78 | ) | 78 | ) |
| 79 | 79 | ||
| 80 | // Error code to Error structure map | 80 | // APIError code to Error structure map |
| 81 | var errorCodeResponse = map[int]Error{ | 81 | var errorCodeResponse = map[int]APIError{ |
| 82 | InvalidMaxUploads: { | 82 | InvalidMaxUploads: { |
| 83 | Code: "InvalidArgument", | 83 | Code: "InvalidArgument", |
| 84 | Description: "Argument maxUploads must be an integer between 0 and 2147483647.", | 84 | Description: "Argument maxUploads must be an integer between 0 and 2147483647.", |
| @@ -232,14 +232,14 @@ var errorCodeResponse = map[int]Error{ · 147 unmodified lines | |||
| 232 | } | 232 | } |
| 233 | 233 | ||
| 234 | // errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown | 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 | return errorCodeResponse[code] | 236 | return errorCodeResponse[code] |
| 237 | } | 237 | } |
| 238 | 238 | ||
| 239 | // getErrorResponse gets in standard error and resource value and | 239 | // getErrorResponse gets in standard error and resource value and |
| 240 | // provides a encodable populated response values | 240 | // provides a encodable populated response values |
| 241 | func getErrorResponse(err Error, resource string) ErrorResponse { | 241 | func getErrorResponse(err APIError, resource string) APIErrorResponse { |
| 242 | var data = ErrorResponse{} | 242 | var data = APIErrorResponse{} |
| 243 | data.Code = err.Code | 243 | data.Code = err.Code |
| 244 | data.Message = err.Description | 244 | data.Message = err.Description |
| 245 | if resource != "" { | 245 | if resource != "" { |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "errors" | 20 | "errors" |
| @@ -44,10 +44,6 @@ type resourceHandler struct { · 23 unmodified lines | |||
| 44 | handler http.Handler | 44 | handler http.Handler |
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | const ( | ||
| 48 | iso8601Format = "20060102T150405Z" | ||
| 49 | ) | ||
| 50 | |||
| 51 | func parseDate(req *http.Request) (time.Time, error) { | 47 | func parseDate(req *http.Request) (time.Time, error) { |
| 52 | amzDate := req.Header.Get(http.CanonicalHeaderKey("x-amz-date")) | 48 | amzDate := req.Header.Get(http.CanonicalHeaderKey("x-amz-date")) |
| 53 | switch { | 49 | switch { |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "bytes" | 20 | "bytes" |
| @@ -22,6 +22,7 @@ import ( · 1 unmodified line | |||
| 22 | "encoding/json" | 22 | "encoding/json" |
| 23 | "encoding/xml" | 23 | "encoding/xml" |
| 24 | "net/http" | 24 | "net/http" |
| 25 | "runtime" | ||
| 25 | "strconv" | 26 | "strconv" |
| 26 | 27 | ||
| 27 | "github.com/minio/minio/pkg/donut" | 28 | "github.com/minio/minio/pkg/donut" |
| @@ -51,9 +52,7 @@ func generateRequestID() []byte { · 23 unmodified lines | |||
| 51 | func setCommonHeaders(w http.ResponseWriter, acceptsType string, contentLength int) { | 52 | func setCommonHeaders(w http.ResponseWriter, acceptsType string, contentLength int) { |
| 52 | // set unique request ID for each reply | 53 | // set unique request ID for each reply |
| 53 | w.Header().Set("X-Amz-Request-Id", string(generateRequestID())) | 54 | w.Header().Set("X-Amz-Request-Id", string(generateRequestID())) |
| 54 | 55 | w.Header().Set("Server", ("Minio/" + minioReleaseTag + " (" + runtime.GOOS + ";" + runtime.GOARCH + ")")) | |
| 55 | // TODO: Modularity comes in the way of passing global state like "version". A better approach needed here. -ab | ||
| 56 | // w.Header().Set("Server", ("Minio/" + version + " (" + runtime.GOOS + ";" + runtime.GOARCH + ")")) | ||
| 57 | 56 | ||
| 58 | w.Header().Set("Accept-Ranges", "bytes") | 57 | w.Header().Set("Accept-Ranges", "bytes") |
| 59 | w.Header().Set("Content-Type", acceptsType) | 58 | w.Header().Set("Content-Type", acceptsType) |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "errors" | 20 | "errors" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "bytes" | 20 | "bytes" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| @@ -33,10 +33,10 @@ const ( · 12 unmodified lines | |||
| 33 | // ---------- | 33 | // ---------- |
| 34 | // This implementation of the GET operation retrieves object. To use GET, | 34 | // This implementation of the GET operation retrieves object. To use GET, |
| 35 | // you must have READ access to the object. | 35 | // you must have READ access to the object. |
| 36 | func (api Minio) GetObjectHandler(w http.ResponseWriter, req *http.Request) { | 36 | func (api MinioAPI) GetObjectHandler(w http.ResponseWriter, req *http.Request) { |
| 37 | // ticket master block | 37 | // ticket master block |
| 38 | { | 38 | { |
| 39 | op := Operation{} | 39 | op := APIOperation{} |
| 40 | op.ProceedCh = make(chan struct{}) | 40 | op.ProceedCh = make(chan struct{}) |
| 41 | api.OP <- op | 41 | api.OP <- op |
| 42 | // block until Ticket master gives us a go | 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 | // HeadObjectHandler - HEAD Object | 100 | // HeadObjectHandler - HEAD Object |
| 101 | // ----------- | 101 | // ----------- |
| 102 | // The HEAD operation retrieves metadata from an object without returning the object itself. | 102 | // The HEAD operation retrieves metadata from an object without returning the object itself. |
| 103 | func (api Minio) HeadObjectHandler(w http.ResponseWriter, req *http.Request) { | 103 | func (api MinioAPI) HeadObjectHandler(w http.ResponseWriter, req *http.Request) { |
| 104 | // ticket master block | 104 | // ticket master block |
| 105 | { | 105 | { |
| 106 | op := Operation{} | 106 | op := APIOperation{} |
| 107 | op.ProceedCh = make(chan struct{}) | 107 | op.ProceedCh = make(chan struct{}) |
| 108 | api.OP <- op | 108 | api.OP <- op |
| 109 | // block until Ticket master gives us a go | 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 | // PutObjectHandler - PUT Object | 157 | // PutObjectHandler - PUT Object |
| 158 | // ---------- | 158 | // ---------- |
| 159 | // This implementation of the PUT operation adds an object to a bucket. | 159 | // This implementation of the PUT operation adds an object to a bucket. |
| 160 | func (api Minio) PutObjectHandler(w http.ResponseWriter, req *http.Request) { | 160 | func (api MinioAPI) PutObjectHandler(w http.ResponseWriter, req *http.Request) { |
| 161 | // Ticket master block | 161 | // Ticket master block |
| 162 | { | 162 | { |
| 163 | op := Operation{} | 163 | op := APIOperation{} |
| 164 | op.ProceedCh = make(chan struct{}) | 164 | op.ProceedCh = make(chan struct{}) |
| 165 | api.OP <- op | 165 | api.OP <- op |
| 166 | // block until Ticket master gives us a go | 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 | /// Multipart API | 259 | /// Multipart API |
| 260 | 260 | ||
| 261 | // NewMultipartUploadHandler - New multipart upload | 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 | // Ticket master block | 263 | // Ticket master block |
| 264 | { | 264 | { |
| 265 | op := Operation{} | 265 | op := APIOperation{} |
| 266 | op.ProceedCh = make(chan struct{}) | 266 | op.ProceedCh = make(chan struct{}) |
| 267 | api.OP <- op | 267 | api.OP <- op |
| 268 | // block until Ticket master gives us a go | 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 | // PutObjectPartHandler - Upload part | 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 | // Ticket master block | 321 | // Ticket master block |
| 322 | { | 322 | { |
| 323 | op := Operation{} | 323 | op := APIOperation{} |
| 324 | op.ProceedCh = make(chan struct{}) | 324 | op.ProceedCh = make(chan struct{}) |
| 325 | api.OP <- op | 325 | api.OP <- op |
| 326 | // block until Ticket master gives us a go | 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 | // AbortMultipartUploadHandler - Abort multipart upload | 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 | // Ticket master block | 421 | // Ticket master block |
| 422 | { | 422 | { |
| 423 | op := Operation{} | 423 | op := APIOperation{} |
| 424 | op.ProceedCh = make(chan struct{}) | 424 | op.ProceedCh = make(chan struct{}) |
| 425 | api.OP <- op | 425 | api.OP <- op |
| 426 | // block until Ticket master gives us a go | 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 | // ListObjectPartsHandler - List object parts | 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 | // Ticket master block | 471 | // Ticket master block |
| 472 | { | 472 | { |
| 473 | op := Operation{} | 473 | op := APIOperation{} |
| 474 | op.ProceedCh = make(chan struct{}) | 474 | op.ProceedCh = make(chan struct{}) |
| 475 | api.OP <- op | 475 | api.OP <- op |
| 476 | // block until Ticket master gives us a go | 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 | // CompleteMultipartUploadHandler - Complete multipart upload | 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 | // Ticket master block | 536 | // Ticket master block |
| 537 | { | 537 | { |
| 538 | op := Operation{} | 538 | op := APIOperation{} |
| 539 | op.ProceedCh = make(chan struct{}) | 539 | op.ProceedCh = make(chan struct{}) |
| 540 | api.OP <- op | 540 | api.OP <- op |
| 541 | // block until Ticket master gives us a go | 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 | /// Delete API | 597 | /// Delete API |
| 598 | 598 | ||
| 599 | // DeleteBucketHandler - Delete bucket | 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 | error := getErrorCode(MethodNotAllowed) | 601 | error := getErrorCode(MethodNotAllowed) |
| 602 | w.WriteHeader(error.HTTPStatusCode) | 602 | w.WriteHeader(error.HTTPStatusCode) |
| 603 | } | 603 | } |
| 604 | 604 | ||
| 605 | // DeleteObjectHandler - Delete object | 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 | error := getErrorCode(MethodNotAllowed) | 607 | error := getErrorCode(MethodNotAllowed) |
| 608 | w.WriteHeader(error.HTTPStatusCode) | 608 | w.WriteHeader(error.HTTPStatusCode) |
| 609 | } | 609 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/url" | 20 | "net/url" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "errors" | 20 | "errors" |
| @@ -28,6 +28,8 @@ import ( · 7 unmodified lines | |||
| 28 | 28 | ||
| 29 | const ( | 29 | const ( |
| 30 | authHeaderPrefix = "AWS4-HMAC-SHA256" | 30 | authHeaderPrefix = "AWS4-HMAC-SHA256" |
| 31 | iso8601Format = "20060102T150405Z" | ||
| 32 | yyyymmdd = "20060102" | ||
| 31 | ) | 33 | ) |
| 32 | 34 | ||
| 33 | // getCredentialsFromAuth parse credentials tag from authorization value | 35 | // getCredentialsFromAuth parse credentials tag from authorization value |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import "errors" | 19 | import "errors" |
| 20 | 20 | ||
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "encoding/base64" | 20 | "encoding/base64" |
| @@ -14,30 +14,30 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package api | 17 | package main |
| 18 | 18 | ||
| 19 | import "github.com/minio/minio/pkg/donut" | 19 | import "github.com/minio/minio/pkg/donut" |
| 20 | 20 | ||
| 21 | // Operation container for individual operations read by Ticket Master | 21 | // APIOperation container for individual operations read by Ticket Master |
| 22 | type Operation struct { | 22 | type APIOperation struct { |
| 23 | ProceedCh chan struct{} | 23 | ProceedCh chan struct{} |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | // Minio container for API and also carries OP (operation) channel | 26 | // MinioAPI container for API and also carries OP (operation) channel |
| 27 | type Minio struct { | 27 | type MinioAPI struct { |
| 28 | OP chan Operation | 28 | OP chan APIOperation |
| 29 | Donut donut.Interface | 29 | Donut donut.Interface |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | // New instantiate a new minio API | 32 | // NewAPI instantiate a new minio API |
| 33 | func New() Minio { | 33 | func NewAPI() MinioAPI { |
| 34 | // ignore errors for now | 34 | // ignore errors for now |
| 35 | d, err := donut.New() | 35 | d, err := donut.New() |
| 36 | if err != nil { | 36 | if err != nil { |
| 37 | panic(err) | 37 | panic(err) |
| 38 | } | 38 | } |
| 39 | return Minio{ | 39 | return MinioAPI{ |
| 40 | OP: make(chan Operation), | 40 | OP: make(chan APIOperation), |
| 41 | Donut: d, | 41 | Donut: d, |
| 42 | } | 42 | } |
| 43 | } | 43 | } |
| @@ -16,11 +16,7 @@ · 15 unmodified lines | |||
| 16 | 16 | ||
| 17 | package main | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import "github.com/minio/cli" |
| 20 | "github.com/minio/cli" | ||
| 21 | "github.com/minio/minio/pkg/server" | ||
| 22 | "github.com/minio/minio/pkg/server/api" | ||
| 23 | ) | ||
| 24 | 20 | ||
| 25 | var serverCmd = cli.Command{ | 21 | var serverCmd = cli.Command{ |
| 26 | Name: "server", | 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 | certFile := c.GlobalString("cert") | 39 | certFile := c.GlobalString("cert") |
| 44 | keyFile := c.GlobalString("key") | 40 | keyFile := c.GlobalString("key") |
| 45 | if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { | 41 | if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { |
| 46 | Fatalln("Both certificate and key are required to enable https.") | 42 | Fatalln("Both certificate and key are required to enable https.") |
| 47 | } | 43 | } |
| 48 | tls := (certFile != "" && keyFile != "") | 44 | tls := (certFile != "" && keyFile != "") |
| 49 | return api.Config{ | 45 | return APIConfig{ |
| 50 | Address: c.GlobalString("address"), | 46 | Address: c.GlobalString("address"), |
| 51 | TLS: tls, | 47 | TLS: tls, |
| 52 | CertFile: certFile, | 48 | CertFile: certFile, |
| @@ -61,6 +57,6 @@ func serverMain(c *cli.Context) { · 8 unmodified lines | |||
| 61 | } | 57 | } |
| 62 | 58 | ||
| 63 | apiServerConfig := getServerConfig(c) | 59 | apiServerConfig := getServerConfig(c) |
| 64 | err := server.Start(apiServerConfig) | 60 | err := StartServer(apiServerConfig) |
| 65 | errorIf(err.Trace(), "Failed to start the minio server.", nil) | 61 | errorIf(err.Trace(), "Failed to start the minio server.", nil) |
| 66 | } | 62 | } |
| @@ -14,17 +14,16 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package server | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "net/http" | 20 | "net/http" |
| 21 | 21 | ||
| 22 | router "github.com/gorilla/mux" | 22 | router "github.com/gorilla/mux" |
| 23 | "github.com/minio/minio/pkg/server/api" | ||
| 24 | ) | 23 | ) |
| 25 | 24 | ||
| 26 | // registerAPI - register all the object API handlers to their respective paths | 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 | mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET") | 27 | mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET") |
| 29 | mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET") | 28 | mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET") |
| 30 | mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT") | 29 | mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT") |
| @@ -45,7 +44,7 @@ func registerAPI(mux *router.Router, a api.Minio) { · 14 unmodified lines | |||
| 45 | mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE") | 44 | mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE") |
| 46 | } | 45 | } |
| 47 | 46 | ||
| 48 | func registerCustomMiddleware(mux *router.Router, mwHandlers ...api.MiddlewareHandler) http.Handler { | 47 | func registerCustomMiddleware(mux *router.Router, mwHandlers ...MiddlewareHandler) http.Handler { |
| 49 | var f http.Handler | 48 | var f http.Handler |
| 50 | f = mux | 49 | f = mux |
| 51 | for _, mw := range mwHandlers { | 50 | for _, mw := range mwHandlers { |
| @@ -55,18 +54,18 @@ func registerCustomMiddleware(mux *router.Router, mwHandlers ...api.MiddlewareHa · 3 unmodified lines | |||
| 55 | } | 54 | } |
| 56 | 55 | ||
| 57 | // getAPIHandler api handler | 56 | // getAPIHandler api handler |
| 58 | func getAPIHandler(conf api.Config) (http.Handler, api.Minio) { | 57 | func getAPIHandler(conf APIConfig) (http.Handler, MinioAPI) { |
| 59 | var mwHandlers = []api.MiddlewareHandler{ | 58 | var mwHandlers = []MiddlewareHandler{ |
| 60 | api.ValidContentTypeHandler, | 59 | ValidContentTypeHandler, |
| 61 | api.TimeValidityHandler, | 60 | TimeValidityHandler, |
| 62 | api.IgnoreResourcesHandler, | 61 | IgnoreResourcesHandler, |
| 63 | api.ValidateAuthHeaderHandler, | 62 | ValidateAuthHeaderHandler, |
| 64 | // api.LoggingHandler, // Disabled logging until we bring in external logging support | 63 | // api.LoggingHandler, // Disabled logging until we bring in external logging support |
| 65 | api.CorsHandler, | 64 | CorsHandler, |
| 66 | } | 65 | } |
| 67 | 66 | ||
| 68 | mux := router.NewRouter() | 67 | mux := router.NewRouter() |
| 69 | minioAPI := api.New() | 68 | minioAPI := NewAPI() |
| 70 | registerAPI(mux, minioAPI) | 69 | registerAPI(mux, minioAPI) |
| 71 | apiHandler := registerCustomMiddleware(mux, mwHandlers...) | 70 | apiHandler := registerCustomMiddleware(mux, mwHandlers...) |
| 72 | return apiHandler, minioAPI | 71 | return apiHandler, minioAPI |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package server | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "bytes" | 20 | "bytes" |
| @@ -28,20 +28,8 @@ import ( · 7 unmodified lines | |||
| 28 | "regexp" | 28 | "regexp" |
| 29 | "sort" | 29 | "sort" |
| 30 | "strings" | 30 | "strings" |
| 31 | "testing" | ||
| 32 | "time" | 31 | "time" |
| 33 | "unicode/utf8" | 32 | "unicode/utf8" |
| 34 | |||
| 35 | . "gopkg.in/check.v1" | ||
| 36 | ) | ||
| 37 | |||
| 38 | // Hook up gocheck into the "go test" runner. | ||
| 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 | "aws4_request", | 242 | "aws4_request", |
| 255 | }, "/") | 243 | }, "/") |
| 256 | 244 | ||
| 257 | stringToSign := authHeader + "\n" + t.Format(iso8601Format) + "\n" | 245 | stringToSign := authHeaderPrefix + "\n" + t.Format(iso8601Format) + "\n" |
| 258 | stringToSign = stringToSign + scope + "\n" | 246 | stringToSign = stringToSign + scope + "\n" |
| 259 | stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest))) | 247 | stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest))) |
| 260 | 248 | ||
| @@ -267,7 +255,7 @@ func (s *MyAPISignatureV4Suite) newRequest(method, urlStr string, contentLength · 6 unmodified lines | |||
| 267 | 255 | ||
| 268 | // final Authorization header | 256 | // final Authorization header |
| 269 | parts := []string{ | 257 | parts := []string{ |
| 270 | authHeader + " Credential=" + s.accessKeyID + "/" + scope, | 258 | authHeaderPrefix + " Credential=" + s.accessKeyID + "/" + scope, |
| 271 | "SignedHeaders=" + signedHeaders, | 259 | "SignedHeaders=" + signedHeaders, |
| 272 | "Signature=" + signature, | 260 | "Signature=" + signature, |
| 273 | } | 261 | } |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package server | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "crypto/tls" | 20 | "crypto/tls" |
| @@ -26,11 +26,10 @@ import ( · 5 unmodified lines | |||
| 26 | 26 | ||
| 27 | "github.com/minio/minio/pkg/minhttp" | 27 | "github.com/minio/minio/pkg/minhttp" |
| 28 | "github.com/minio/minio/pkg/probe" | 28 | "github.com/minio/minio/pkg/probe" |
| 29 | "github.com/minio/minio/pkg/server/api" | ||
| 30 | ) | 29 | ) |
| 31 | 30 | ||
| 32 | // getAPI server instance | 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 | // Minio server config | 33 | // Minio server config |
| 35 | httpServer := &http.Server{ | 34 | httpServer := &http.Server{ |
| 36 | Addr: conf.Address, | 35 | Addr: conf.Address, |
| @@ -84,7 +83,7 @@ func getAPIServer(conf api.Config, apiHandler http.Handler) (*http.Server, *prob · 47 unmodified lines | |||
| 84 | } | 83 | } |
| 85 | 84 | ||
| 86 | // Start ticket master | 85 | // Start ticket master |
| 87 | func startTM(a api.Minio) { | 86 | func startTM(a MinioAPI) { |
| 88 | for { | 87 | for { |
| 89 | for op := range a.OP { | 88 | for op := range a.OP { |
| 90 | op.ProceedCh <- struct{}{} | 89 | op.ProceedCh <- struct{}{} |
| @@ -92,8 +91,8 @@ func startTM(a api.Minio) { · 1 unmodified line | |||
| 92 | } | 91 | } |
| 93 | } | 92 | } |
| 94 | 93 | ||
| 95 | // Start starts a s3 compatible cloud storage server | 94 | // StartServer starts an s3 compatible cloud storage server |
| 96 | func Start(conf api.Config) *probe.Error { | 95 | func StartServer(conf APIConfig) *probe.Error { |
| 97 | apiHandler, minioAPI := getAPIHandler(conf) | 96 | apiHandler, minioAPI := getAPIHandler(conf) |
| 98 | apiServer, err := getAPIServer(conf, apiHandler) | 97 | apiServer, err := getAPIServer(conf, apiHandler) |
| 99 | if err != nil { | 98 | if err != nil { |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package server | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "bytes" | 20 | "bytes" |
| @@ -28,7 +28,6 @@ import ( · 7 unmodified lines | |||
| 28 | "net/http/httptest" | 28 | "net/http/httptest" |
| 29 | 29 | ||
| 30 | "github.com/minio/minio/pkg/donut" | 30 | "github.com/minio/minio/pkg/donut" |
| 31 | "github.com/minio/minio/pkg/server/api" | ||
| 32 | . "gopkg.in/check.v1" | 31 | . "gopkg.in/check.v1" |
| 33 | ) | 32 | ) |
| 34 | 33 | ||
| @@ -52,7 +51,7 @@ func (s *MyAPIDonutCacheSuite) SetUpSuite(c *C) { · 17 unmodified lines | |||
| 52 | perr := donut.SaveConfig(conf) | 51 | perr := donut.SaveConfig(conf) |
| 53 | c.Assert(perr, IsNil) | 52 | c.Assert(perr, IsNil) |
| 54 | 53 | ||
| 55 | httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16}) | 54 | httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) |
| 56 | go startTM(minioAPI) | 55 | go startTM(minioAPI) |
| 57 | testAPIDonutCacheServer = httptest.NewServer(httpHandler) | 56 | testAPIDonutCacheServer = httptest.NewServer(httpHandler) |
| 58 | } | 57 | } |
| @@ -319,7 +318,7 @@ func (s *MyAPIDonutCacheSuite) TestListBuckets(c *C) { · 260 unmodified lines | |||
| 319 | c.Assert(err, IsNil) | 318 | c.Assert(err, IsNil) |
| 320 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 319 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 321 | 320 | ||
| 322 | var results api.ListBucketsResponse | 321 | var results ListBucketsResponse |
| 323 | decoder := xml.NewDecoder(response.Body) | 322 | decoder := xml.NewDecoder(response.Body) |
| 324 | err = decoder.Decode(&results) | 323 | err = decoder.Decode(&results) |
| 325 | c.Assert(err, IsNil) | 324 | c.Assert(err, IsNil) |
| @@ -676,7 +675,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartAbort(c *C) { · 350 unmodified lines | |||
| 676 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 675 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 677 | 676 | ||
| 678 | decoder := xml.NewDecoder(response.Body) | 677 | decoder := xml.NewDecoder(response.Body) |
| 679 | newResponse := &api.InitiateMultipartUploadResponse{} | 678 | newResponse := &InitiateMultipartUploadResponse{} |
| 680 | 679 | ||
| 681 | err = decoder.Decode(newResponse) | 680 | err = decoder.Decode(newResponse) |
| 682 | c.Assert(err, IsNil) | 681 | c.Assert(err, IsNil) |
| @@ -721,7 +720,7 @@ func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) { · 38 unmodified lines | |||
| 721 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 720 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 722 | 721 | ||
| 723 | decoder := xml.NewDecoder(response.Body) | 722 | decoder := xml.NewDecoder(response.Body) |
| 724 | newResponse := &api.InitiateMultipartUploadResponse{} | 723 | newResponse := &InitiateMultipartUploadResponse{} |
| 725 | 724 | ||
| 726 | err = decoder.Decode(newResponse) | 725 | err = decoder.Decode(newResponse) |
| 727 | c.Assert(err, IsNil) | 726 | c.Assert(err, IsNil) |
| @@ -750,7 +749,7 @@ func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) { · 22 unmodified lines | |||
| 750 | c.Assert(response3.StatusCode, Equals, http.StatusOK) | 749 | c.Assert(response3.StatusCode, Equals, http.StatusOK) |
| 751 | 750 | ||
| 752 | decoder = xml.NewDecoder(response3.Body) | 751 | decoder = xml.NewDecoder(response3.Body) |
| 753 | newResponse3 := &api.ListMultipartUploadsResponse{} | 752 | newResponse3 := &ListMultipartUploadsResponse{} |
| 754 | err = decoder.Decode(newResponse3) | 753 | err = decoder.Decode(newResponse3) |
| 755 | c.Assert(err, IsNil) | 754 | c.Assert(err, IsNil) |
| 756 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") | 755 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") |
| @@ -772,7 +771,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartList(c *C) { · 15 unmodified lines | |||
| 772 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 771 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 773 | 772 | ||
| 774 | decoder := xml.NewDecoder(response.Body) | 773 | decoder := xml.NewDecoder(response.Body) |
| 775 | newResponse := &api.InitiateMultipartUploadResponse{} | 774 | newResponse := &InitiateMultipartUploadResponse{} |
| 776 | 775 | ||
| 777 | err = decoder.Decode(newResponse) | 776 | err = decoder.Decode(newResponse) |
| 778 | c.Assert(err, IsNil) | 777 | c.Assert(err, IsNil) |
| @@ -826,7 +825,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) { · 47 unmodified lines | |||
| 826 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 825 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 827 | 826 | ||
| 828 | decoder := xml.NewDecoder(response.Body) | 827 | decoder := xml.NewDecoder(response.Body) |
| 829 | newResponse := &api.InitiateMultipartUploadResponse{} | 828 | newResponse := &InitiateMultipartUploadResponse{} |
| 830 | 829 | ||
| 831 | err = decoder.Decode(newResponse) | 830 | err = decoder.Decode(newResponse) |
| 832 | c.Assert(err, IsNil) | 831 | c.Assert(err, IsNil) |
| @@ -888,7 +887,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) { · 55 unmodified lines | |||
| 888 | func verifyError(c *C, response *http.Response, code, description string, statusCode int) { | 887 | func verifyError(c *C, response *http.Response, code, description string, statusCode int) { |
| 889 | data, err := ioutil.ReadAll(response.Body) | 888 | data, err := ioutil.ReadAll(response.Body) |
| 890 | c.Assert(err, IsNil) | 889 | c.Assert(err, IsNil) |
| 891 | errorResponse := api.ErrorResponse{} | 890 | errorResponse := APIErrorResponse{} |
| 892 | err = xml.Unmarshal(data, &errorResponse) | 891 | err = xml.Unmarshal(data, &errorResponse) |
| 893 | c.Assert(err, IsNil) | 892 | c.Assert(err, IsNil) |
| 894 | c.Assert(errorResponse.Code, Equals, code) | 893 | c.Assert(errorResponse.Code, Equals, code) |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package server | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "bytes" | 20 | "bytes" |
| @@ -29,7 +29,6 @@ import ( · 8 unmodified lines | |||
| 29 | "net/http/httptest" | 29 | "net/http/httptest" |
| 30 | 30 | ||
| 31 | "github.com/minio/minio/pkg/donut" | 31 | "github.com/minio/minio/pkg/donut" |
| 32 | "github.com/minio/minio/pkg/server/api" | ||
| 33 | . "gopkg.in/check.v1" | 32 | . "gopkg.in/check.v1" |
| 34 | ) | 33 | ) |
| 35 | 34 | ||
| @@ -71,7 +70,7 @@ func (s *MyAPIDonutSuite) SetUpSuite(c *C) { · 35 unmodified lines | |||
| 71 | perr := donut.SaveConfig(conf) | 70 | perr := donut.SaveConfig(conf) |
| 72 | c.Assert(perr, IsNil) | 71 | c.Assert(perr, IsNil) |
| 73 | 72 | ||
| 74 | httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16}) | 73 | httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) |
| 75 | go startTM(minioAPI) | 74 | go startTM(minioAPI) |
| 76 | testAPIDonutServer = httptest.NewServer(httpHandler) | 75 | testAPIDonutServer = httptest.NewServer(httpHandler) |
| 77 | } | 76 | } |
| @@ -338,7 +337,7 @@ func (s *MyAPIDonutSuite) TestListBuckets(c *C) { · 260 unmodified lines | |||
| 338 | c.Assert(err, IsNil) | 337 | c.Assert(err, IsNil) |
| 339 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 338 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 340 | 339 | ||
| 341 | var results api.ListBucketsResponse | 340 | var results ListBucketsResponse |
| 342 | decoder := xml.NewDecoder(response.Body) | 341 | decoder := xml.NewDecoder(response.Body) |
| 343 | err = decoder.Decode(&results) | 342 | err = decoder.Decode(&results) |
| 344 | c.Assert(err, IsNil) | 343 | c.Assert(err, IsNil) |
| @@ -696,7 +695,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartAbort(c *C) { · 351 unmodified lines | |||
| 696 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 695 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 697 | 696 | ||
| 698 | decoder := xml.NewDecoder(response.Body) | 697 | decoder := xml.NewDecoder(response.Body) |
| 699 | newResponse := &api.InitiateMultipartUploadResponse{} | 698 | newResponse := &InitiateMultipartUploadResponse{} |
| 700 | 699 | ||
| 701 | err = decoder.Decode(newResponse) | 700 | err = decoder.Decode(newResponse) |
| 702 | c.Assert(err, IsNil) | 701 | c.Assert(err, IsNil) |
| @@ -741,7 +740,7 @@ func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) { · 38 unmodified lines | |||
| 741 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 740 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 742 | 741 | ||
| 743 | decoder := xml.NewDecoder(response.Body) | 742 | decoder := xml.NewDecoder(response.Body) |
| 744 | newResponse := &api.InitiateMultipartUploadResponse{} | 743 | newResponse := &InitiateMultipartUploadResponse{} |
| 745 | 744 | ||
| 746 | err = decoder.Decode(newResponse) | 745 | err = decoder.Decode(newResponse) |
| 747 | c.Assert(err, IsNil) | 746 | c.Assert(err, IsNil) |
| @@ -770,7 +769,7 @@ func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) { · 22 unmodified lines | |||
| 770 | c.Assert(response3.StatusCode, Equals, http.StatusOK) | 769 | c.Assert(response3.StatusCode, Equals, http.StatusOK) |
| 771 | 770 | ||
| 772 | decoder = xml.NewDecoder(response3.Body) | 771 | decoder = xml.NewDecoder(response3.Body) |
| 773 | newResponse3 := &api.ListMultipartUploadsResponse{} | 772 | newResponse3 := &ListMultipartUploadsResponse{} |
| 774 | err = decoder.Decode(newResponse3) | 773 | err = decoder.Decode(newResponse3) |
| 775 | c.Assert(err, IsNil) | 774 | c.Assert(err, IsNil) |
| 776 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") | 775 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") |
| @@ -792,7 +791,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartList(c *C) { · 15 unmodified lines | |||
| 792 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 791 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 793 | 792 | ||
| 794 | decoder := xml.NewDecoder(response.Body) | 793 | decoder := xml.NewDecoder(response.Body) |
| 795 | newResponse := &api.InitiateMultipartUploadResponse{} | 794 | newResponse := &InitiateMultipartUploadResponse{} |
| 796 | 795 | ||
| 797 | err = decoder.Decode(newResponse) | 796 | err = decoder.Decode(newResponse) |
| 798 | c.Assert(err, IsNil) | 797 | c.Assert(err, IsNil) |
| @@ -846,7 +845,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipart(c *C) { · 47 unmodified lines | |||
| 846 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 845 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 847 | 846 | ||
| 848 | decoder := xml.NewDecoder(response.Body) | 847 | decoder := xml.NewDecoder(response.Body) |
| 849 | newResponse := &api.InitiateMultipartUploadResponse{} | 848 | newResponse := &InitiateMultipartUploadResponse{} |
| 850 | 849 | ||
| 851 | err = decoder.Decode(newResponse) | 850 | err = decoder.Decode(newResponse) |
| 852 | c.Assert(err, IsNil) | 851 | c.Assert(err, IsNil) |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | package server | 17 | package main |
| 18 | 18 | ||
| 19 | import ( | 19 | import ( |
| 20 | "bytes" | 20 | "bytes" |
| @@ -30,7 +30,6 @@ import ( · 9 unmodified lines | |||
| 30 | 30 | ||
| 31 | "github.com/minio/minio/pkg/auth" | 31 | "github.com/minio/minio/pkg/auth" |
| 32 | "github.com/minio/minio/pkg/donut" | 32 | "github.com/minio/minio/pkg/donut" |
| 33 | "github.com/minio/minio/pkg/server/api" | ||
| 34 | . "gopkg.in/check.v1" | 33 | . "gopkg.in/check.v1" |
| 35 | ) | 34 | ) |
| 36 | 35 | ||
| @@ -79,7 +78,7 @@ func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) { · 42 unmodified lines | |||
| 79 | perr = auth.SaveConfig(authConf) | 78 | perr = auth.SaveConfig(authConf) |
| 80 | c.Assert(perr, IsNil) | 79 | c.Assert(perr, IsNil) |
| 81 | 80 | ||
| 82 | httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16}) | 81 | httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) |
| 83 | go startTM(minioAPI) | 82 | go startTM(minioAPI) |
| 84 | testSignatureV4Server = httptest.NewServer(httpHandler) | 83 | testSignatureV4Server = httptest.NewServer(httpHandler) |
| 85 | } | 84 | } |
| @@ -347,7 +346,7 @@ func (s *MyAPISignatureV4Suite) TestListBuckets(c *C) { · 261 unmodified lines | |||
| 347 | c.Assert(err, IsNil) | 346 | c.Assert(err, IsNil) |
| 348 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 347 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 349 | 348 | ||
| 350 | var results api.ListBucketsResponse | 349 | var results ListBucketsResponse |
| 351 | decoder := xml.NewDecoder(response.Body) | 350 | decoder := xml.NewDecoder(response.Body) |
| 352 | err = decoder.Decode(&results) | 351 | err = decoder.Decode(&results) |
| 353 | c.Assert(err, IsNil) | 352 | c.Assert(err, IsNil) |
| @@ -689,7 +688,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipartAbort(c *C) { · 335 unmodified lines | |||
| 689 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 688 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 690 | 689 | ||
| 691 | decoder := xml.NewDecoder(response.Body) | 690 | decoder := xml.NewDecoder(response.Body) |
| 692 | newResponse := &api.InitiateMultipartUploadResponse{} | 691 | newResponse := &InitiateMultipartUploadResponse{} |
| 693 | 692 | ||
| 694 | err = decoder.Decode(newResponse) | 693 | err = decoder.Decode(newResponse) |
| 695 | c.Assert(err, IsNil) | 694 | c.Assert(err, IsNil) |
| @@ -736,7 +735,7 @@ func (s *MyAPISignatureV4Suite) TestBucketMultipartList(c *C) { · 40 unmodified lines | |||
| 736 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 735 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 737 | 736 | ||
| 738 | decoder := xml.NewDecoder(response.Body) | 737 | decoder := xml.NewDecoder(response.Body) |
| 739 | newResponse := &api.InitiateMultipartUploadResponse{} | 738 | newResponse := &InitiateMultipartUploadResponse{} |
| 740 | 739 | ||
| 741 | err = decoder.Decode(newResponse) | 740 | err = decoder.Decode(newResponse) |
| 742 | c.Assert(err, IsNil) | 741 | c.Assert(err, IsNil) |
| @@ -767,7 +766,7 @@ func (s *MyAPISignatureV4Suite) TestBucketMultipartList(c *C) { · 24 unmodified lines | |||
| 767 | c.Assert(response3.StatusCode, Equals, http.StatusOK) | 766 | c.Assert(response3.StatusCode, Equals, http.StatusOK) |
| 768 | 767 | ||
| 769 | decoder = xml.NewDecoder(response3.Body) | 768 | decoder = xml.NewDecoder(response3.Body) |
| 770 | newResponse3 := &api.ListMultipartUploadsResponse{} | 769 | newResponse3 := &ListMultipartUploadsResponse{} |
| 771 | err = decoder.Decode(newResponse3) | 770 | err = decoder.Decode(newResponse3) |
| 772 | c.Assert(err, IsNil) | 771 | c.Assert(err, IsNil) |
| 773 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") | 772 | c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist") |
| @@ -789,7 +788,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipartList(c *C) { · 15 unmodified lines | |||
| 789 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 788 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 790 | 789 | ||
| 791 | decoder := xml.NewDecoder(response.Body) | 790 | decoder := xml.NewDecoder(response.Body) |
| 792 | newResponse := &api.InitiateMultipartUploadResponse{} | 791 | newResponse := &InitiateMultipartUploadResponse{} |
| 793 | 792 | ||
| 794 | err = decoder.Decode(newResponse) | 793 | err = decoder.Decode(newResponse) |
| 795 | c.Assert(err, IsNil) | 794 | c.Assert(err, IsNil) |
| @@ -845,7 +844,7 @@ func (s *MyAPISignatureV4Suite) TestObjectMultipart(c *C) { · 49 unmodified lines | |||
| 845 | c.Assert(response.StatusCode, Equals, http.StatusOK) | 844 | c.Assert(response.StatusCode, Equals, http.StatusOK) |
| 846 | 845 | ||
| 847 | decoder := xml.NewDecoder(response.Body) | 846 | decoder := xml.NewDecoder(response.Body) |
| 848 | newResponse := &api.InitiateMultipartUploadResponse{} | 847 | newResponse := &InitiateMultipartUploadResponse{} |
| 849 | 848 | ||
| 850 | err = decoder.Decode(newResponse) | 849 | err = decoder.Decode(newResponse) |
| 851 | c.Assert(err, IsNil) | 850 | c.Assert(err, IsNil) |
| @@ -99,6 +99,34 @@ · 98 unmodified lines | |||
| 99 | "local": "vendor/gopkg.in/check.v1", | 99 | "local": "vendor/gopkg.in/check.v1", |
| 100 | "revision": "11d3bc7aa68e238947792f30573146a3231fc0f1", | 100 | "revision": "11d3bc7aa68e238947792f30573146a3231fc0f1", |
| 101 | "revisionTime": "2015-07-29T10:04:31+02:00" | 101 | "revisionTime": "2015-07-29T10:04:31+02:00" |
| 102 | }, | ||
| 103 | { | ||
| 104 | "canonical": "gopkg.in/mgo.v2", | ||
| 105 | "comment": "", | ||
| 106 | "local": "vendor/gopkg.in/mgo.v2", | ||
| 107 | "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c", | ||
| 108 | "revisionTime": "2015-08-21T12:30:02-03:00" | ||
| 109 | }, | ||
| 110 | { | ||
| 111 | "canonical": "gopkg.in/mgo.v2/bson", | ||
| 112 | "comment": "", | ||
| 113 | "local": "vendor/gopkg.in/mgo.v2/bson", | ||
| 114 | "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c", | ||
| 115 | "revisionTime": "2015-08-21T12:30:02-03:00" | ||
| 116 | }, | ||
| 117 | { | ||
| 118 | "canonical": "gopkg.in/mgo.v2/internal/sasl", | ||
| 119 | "comment": "", | ||
| 120 | "local": "vendor/gopkg.in/mgo.v2/internal/sasl", | ||
| 121 | "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c", | ||
| 122 | "revisionTime": "2015-08-21T12:30:02-03:00" | ||
| 123 | }, | ||
| 124 | { | ||
| 125 | "canonical": "gopkg.in/mgo.v2/internal/scram", | ||
| 126 | "comment": "", | ||
| 127 | "local": "vendor/gopkg.in/mgo.v2/internal/scram", | ||
| 128 | "revision": "f4923a569136442e900b8cf5c1a706c0a8b0883c", | ||
| 129 | "revisionTime": "2015-08-21T12:30:02-03:00" | ||
| 102 | } | 130 | } |
| 103 | ] | 131 | ] |
| 104 | } | 132 | } |
| @@ -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.