diff options
author | Thomas Schubert <thomas.schubert@mongodb.com> | 2017-06-06 11:38:42 -0400 |
---|---|---|
committer | Thomas Schubert <thomas.schubert@mongodb.com> | 2017-06-06 11:38:42 -0400 |
commit | 9676bdc2b561f23805769478643e7c026b3ea9bc (patch) | |
tree | c043c1f29ed1e54efc96956352cfca729570943d | |
parent | 17334c154308b7b2fef687c5cb627978241707f8 (diff) | |
download | mongo-9676bdc2b561f23805769478643e7c026b3ea9bc.tar.gz |
Import tools: 1fa8108e8fdd955f7e12550bf2e5a56471c3c73e from branch v3.4
ref: 17fbdf31ab..1fa8108e8f
for: 3.4.5
TOOLS-1605 Conditionally create a Windows job object in smoke.py
TOOLS-1623 merge mgo's fix for "Apparent performance degradation in mongodump"
TOOLS-1635 Mongostat panic error
TOOLS-1645 short archives cause mongorestore to hang instead of failing
TOOLS-1679 stop running mongod's in tests with --nohttpinterface
TOOLS-1682 don't run native cert tests against mongod 3.5 on the 3.4 branch
27 files changed, 631 insertions, 456 deletions
diff --git a/src/mongo/gotools/Godeps b/src/mongo/gotools/Godeps index c5d6318b56c..509c3865580 100644 --- a/src/mongo/gotools/Godeps +++ b/src/mongo/gotools/Godeps @@ -1,4 +1,4 @@ -gopkg.in/mgo.v2 1e52f6152a9b262873f831bb5a94bcd29ef38c38 github.com/10gen/mgo +gopkg.in/mgo.v2 39b4000d99037e917f3a3b9d2dcab667a9ef284a github.com/10gen/mgo gopkg.in/tomb.v2 14b3d72120e8d10ea6e6b7f87f7175734b1faab8 github.com/jtolds/gls 8ddce2a84170772b95dd5d576c48d517b22cac63 github.com/jacobsa/oglematchers 3ecefc49db07722beca986d9bb71ddd026b133f0 diff --git a/src/mongo/gotools/common.yml b/src/mongo/gotools/common.yml index 7453ec02145..79b68563d0d 100644 --- a/src/mongo/gotools/common.yml +++ b/src/mongo/gotools/common.yml @@ -6,7 +6,6 @@ command_type: system # run the same task in the previous revision if the current task fails stepback: true -disable_cleanup: true mongo_tools_variables: @@ -1235,7 +1234,7 @@ tasks: - func: "setup credentials" - func: "download mongod" vars: - mongo_version: "latest" + mongo_version: "3.4" - func: "fetch tool" vars: tool: mongoimport @@ -1738,7 +1737,6 @@ buildvariants: excludes: requires_mmap_available,requires_mongo_24,requires_mongo_26,requires_mongo_30 resmoke_args: -j 2 multiversion_override: "skip" - mongo_version_always_use_latest: true arch: "linux/s390x" edition: enterprise run_kinit: true @@ -1762,7 +1760,6 @@ buildvariants: excludes: requires_mmap_available,requires_large_ram,requires_mongo_24,requires_mongo_26,requires_mongo_30 resmoke_args: -j 2 multiversion_override: "skip" - mongo_version_always_use_latest: true arch: "linux/arm64" edition: ssl integration_test_args: integration diff --git a/src/mongo/gotools/common/archive/demultiplexer.go b/src/mongo/gotools/common/archive/demultiplexer.go index 5b0503b0208..b56cc2132e2 100644 --- a/src/mongo/gotools/common/archive/demultiplexer.go +++ b/src/mongo/gotools/common/archive/demultiplexer.go @@ -23,6 +23,12 @@ type DemuxOut interface { Sum64() (uint64, bool) } +const ( + NamespaceUnopened = iota + NamespaceOpened + NamespaceClosed +) + // Demultiplexer implements Parser. type Demultiplexer struct { In io.Reader @@ -33,6 +39,20 @@ type Demultiplexer struct { buf [db.MaxBSONSize]byte NamespaceChan chan string NamespaceErrorChan chan error + NamespaceStatus map[string]int +} + +func CreateDemux(namespaceMetadatas []*CollectionMetadata, in io.Reader) *Demultiplexer { + demux := &Demultiplexer{ + NamespaceStatus: make(map[string]int), + In: in, + } + for _, cm := range namespaceMetadatas { + ns := cm.Database + "." + cm.Collection + demux.NamespaceStatus[ns] = NamespaceUnopened + + } + return demux } // Run creates and runs a parser with the Demultiplexer as a consumer @@ -42,6 +62,7 @@ func (demux *Demultiplexer) Run() error { if len(demux.outs) > 0 { log.Logvf(log.Always, "demux finishing when there are still outs (%v)", len(demux.outs)) } + log.Logvf(log.DebugLow, "demux finishing (err:%v)", err) return err } @@ -89,6 +110,10 @@ func (demux *Demultiplexer) HeaderBSON(buf []byte) error { } demux.currentNamespace = colHeader.Database + "." + colHeader.Collection if _, ok := demux.outs[demux.currentNamespace]; !ok { + if demux.NamespaceStatus[demux.currentNamespace] != NamespaceUnopened { + return newError("namespace header for already opened namespace") + } + demux.NamespaceStatus[demux.currentNamespace] = NamespaceOpened if demux.NamespaceChan != nil { demux.NamespaceChan <- demux.currentNamespace err := <-demux.NamespaceErrorChan @@ -105,7 +130,11 @@ func (demux *Demultiplexer) HeaderBSON(buf []byte) error { } } if colHeader.EOF { + if rcr, ok := demux.outs[demux.currentNamespace].(*RegularCollectionReceiver); ok { + rcr.err = io.EOF + } demux.outs[demux.currentNamespace].Close() + demux.NamespaceStatus[demux.currentNamespace] = NamespaceClosed length := int64(demux.lengths[demux.currentNamespace]) crcUInt64, ok := demux.outs[demux.currentNamespace].Sum64() if ok { @@ -137,18 +166,30 @@ func (demux *Demultiplexer) HeaderBSON(buf []byte) error { // End is part of the ParserConsumer interface and receives the end of archive notification. func (demux *Demultiplexer) End() error { log.Logvf(log.DebugHigh, "demux End") + var err error if len(demux.outs) != 0 { openNss := []string{} for ns := range demux.outs { openNss = append(openNss, ns) + if rcr, ok := demux.outs[ns].(*RegularCollectionReceiver); ok { + rcr.err = newError("archive io error") + } + demux.outs[ns].Close() + } + err = newError(fmt.Sprintf("archive finished but contained files were unfinished (%v)", openNss)) + } else { + for ns, status := range demux.NamespaceStatus { + if status != NamespaceClosed { + err = newError(fmt.Sprintf("archive finished before all collections were seen (%v)", ns)) + } } - return newError(fmt.Sprintf("archive finished but contained files were unfinished (%v)", openNss)) } if demux.NamespaceChan != nil { close(demux.NamespaceChan) } - return nil + + return err } // BodyBSON is part of the ParserConsumer interface and receives BSON bodies from the parser. @@ -196,6 +237,7 @@ type RegularCollectionReceiver struct { hash hash.Hash64 closeOnce sync.Once openOnce sync.Once + err error } func (receiver *RegularCollectionReceiver) Sum64() (uint64, bool) { @@ -219,7 +261,7 @@ func (receiver *RegularCollectionReceiver) Read(r []byte) (int, error) { wLen, ok := <-receiver.readLenChan if !ok { close(receiver.readBufChan) - return 0, io.EOF + return 0, receiver.err } if wLen > db.MaxBSONSize { return 0, fmt.Errorf("incomming buffer size is too big %v", wLen) diff --git a/src/mongo/gotools/common/archive/multiplexer_roundtrip_test.go b/src/mongo/gotools/common/archive/multiplexer_roundtrip_test.go index 025121b704a..18fd73729db 100644 --- a/src/mongo/gotools/common/archive/multiplexer_roundtrip_test.go +++ b/src/mongo/gotools/common/archive/multiplexer_roundtrip_test.go @@ -86,7 +86,10 @@ func TestBasicMux(t *testing.T) { err = <-mux.Completed So(err, ShouldBeNil) - demux := &Demultiplexer{In: buf} + demux := &Demultiplexer{ + In: buf, + NamespaceStatus: make(map[string]int), + } demuxOuts := map[string]*RegularCollectionReceiver{} errChan := make(chan error) @@ -121,7 +124,10 @@ func TestParallelMux(t *testing.T) { mux := NewMultiplexer(writePipe, new(testNotifier)) muxIns := map[string]*MuxIn{} - demux := &Demultiplexer{In: readPipe} + demux := &Demultiplexer{ + In: readPipe, + NamespaceStatus: make(map[string]int), + } demuxOuts := map[string]*RegularCollectionReceiver{} inChecksum := map[string]hash.Hash{} diff --git a/src/mongo/gotools/common/archive/parser.go b/src/mongo/gotools/common/archive/parser.go index 2dd75807a8b..8be0529a6dc 100644 --- a/src/mongo/gotools/common/archive/parser.go +++ b/src/mongo/gotools/common/archive/parser.go @@ -104,8 +104,9 @@ func (parse *Parser) ReadAllBlocks(consumer ParserConsumer) (err error) { for err == nil { err = parse.ReadBlock(consumer) } + endError := consumer.End() if err == io.EOF { - return nil + return endError } return err } @@ -119,13 +120,6 @@ func (parse *Parser) ReadAllBlocks(consumer ParserConsumer) (err error) { // parsing failure. func (parse *Parser) ReadBlock(consumer ParserConsumer) (err error) { isTerminator, err := parse.readBSONOrTerminator() - if err == io.EOF { - handlerErr := consumer.End() - if handlerErr != nil { - return newParserWrappedError("ParserConsumer.End", handlerErr) - } - return err - } if err != nil { return err } diff --git a/src/mongo/gotools/common/archive/parser_test.go b/src/mongo/gotools/common/archive/parser_test.go index dba7e1d0263..bcbe2880f10 100644 --- a/src/mongo/gotools/common/archive/parser_test.go +++ b/src/mongo/gotools/common/archive/parser_test.go @@ -50,7 +50,7 @@ func TestParsing(t *testing.T) { Convey("With a parser with a simple parser consumer", t, func() { tc := &testConsumer{} parser := Parser{} - Convey("a well formed header and body data parse correctly", func() { + Convey("a well formed header and body", func() { buf := bytes.Buffer{} b, _ := bson.Marshal(strStruct{"header"}) buf.Write(b) @@ -58,15 +58,24 @@ func TestParsing(t *testing.T) { buf.Write(b) buf.Write(term) parser.In = &buf - err := parser.ReadBlock(tc) - So(err, ShouldBeNil) - So(tc.eof, ShouldBeFalse) - So(tc.headers[0], ShouldEqual, "header") - So(tc.bodies[0], ShouldEqual, "body") + Convey("ReadBlock data parses correctly", func() { + err := parser.ReadBlock(tc) + So(err, ShouldBeNil) + So(tc.eof, ShouldBeFalse) + So(tc.headers[0], ShouldEqual, "header") + So(tc.bodies[0], ShouldEqual, "body") - err = parser.ReadBlock(tc) - So(err, ShouldEqual, io.EOF) - So(tc.eof, ShouldBeTrue) + err = parser.ReadBlock(tc) + So(err, ShouldEqual, io.EOF) + }) + Convey("ReadAllBlock data parses correctly", func() { + err := parser.ReadAllBlocks(tc) + So(err, ShouldEqual, nil) + So(tc.eof, ShouldBeTrue) + So(tc.headers[0], ShouldEqual, "header") + So(tc.bodies[0], ShouldEqual, "body") + + }) }) Convey("a well formed header and multiple body datas parse correctly", func() { buf := bytes.Buffer{} @@ -90,7 +99,7 @@ func TestParsing(t *testing.T) { err = parser.ReadBlock(tc) So(err, ShouldEqual, io.EOF) - So(tc.eof, ShouldBeTrue) + So(tc.eof, ShouldBeFalse) }) Convey("an incorrect terminator should cause an error", func() { buf := bytes.Buffer{} @@ -108,13 +117,13 @@ func TestParsing(t *testing.T) { parser.In = &buf err := parser.ReadBlock(tc) So(err, ShouldEqual, io.EOF) - So(tc.eof, ShouldBeTrue) + So(tc.eof, ShouldBeFalse) }) Convey("an error comming from the consumer should propigate through the parser", func() { tc.eof = true buf := bytes.Buffer{} parser.In = &buf - err := parser.ReadBlock(tc) + err := parser.ReadAllBlocks(tc) So(err.Error(), ShouldContainSubstring, "double end") }) Convey("a partial block should result in a non-EOF error", func() { diff --git a/src/mongo/gotools/import.data b/src/mongo/gotools/import.data index d261a42e87c..cd1daead6bc 100644 --- a/src/mongo/gotools/import.data +++ b/src/mongo/gotools/import.data @@ -1,5 +1,6 @@ { - "commit": "17fbdf31abca50cdfe27482b05b1476f42ecab0a", + "commit": "1fa8108e8fdd955f7e12550bf2e5a56471c3c73e", "github": "mongodb/mongo-tools.git", + "vendor": "tools", "branch": "v3.4" } diff --git a/src/mongo/gotools/mongorestore/mongorestore.go b/src/mongo/gotools/mongorestore/mongorestore.go index 3e96bba0b0e..c79b04e5acf 100644 --- a/src/mongo/gotools/mongorestore/mongorestore.go +++ b/src/mongo/gotools/mongorestore/mongorestore.go @@ -248,13 +248,15 @@ func (restore *MongoRestore) Restore() error { } if restore.InputOptions.Archive != "" { - archiveReader, err := restore.getArchiveReader() - if err != nil { - return err - } - restore.archive = &archive.Reader{ - In: archiveReader, - Prelude: &archive.Prelude{}, + if restore.archive == nil { + archiveReader, err := restore.getArchiveReader() + if err != nil { + return err + } + restore.archive = &archive.Reader{ + In: archiveReader, + Prelude: &archive.Prelude{}, + } } err = restore.archive.Prelude.Read(restore.archive.In) if err != nil { @@ -316,9 +318,7 @@ func (restore *MongoRestore) Restore() error { // Create the demux before intent creation, because muted archive intents need // to register themselves with the demux directly if restore.InputOptions.Archive != "" { - restore.archive.Demux = &archive.Demultiplexer{ - In: restore.archive.In, - } + restore.archive.Demux = archive.CreateDemux(restore.archive.Prelude.NamespaceMetadatas, restore.archive.In) } switch { @@ -386,13 +386,18 @@ func (restore *MongoRestore) Restore() error { return nil } + demuxFinished := make(chan interface{}) + var demuxErr error if restore.InputOptions.Archive != "" { namespaceChan := make(chan string, 1) namespaceErrorChan := make(chan error) restore.archive.Demux.NamespaceChan = namespaceChan restore.archive.Demux.NamespaceErrorChan = namespaceErrorChan - go restore.archive.Demux.Run() + go func() { + demuxErr = restore.archive.Demux.Run() + close(demuxFinished) + }() // consume the new namespace announcement from the demux for all of the special collections // that get cached when being read out of the archive. // The first regular collection found gets pushed back on to the namespaceChan @@ -481,7 +486,12 @@ func (restore *MongoRestore) Restore() error { } } - log.Logv(log.Always, "done") + defer log.Logv(log.Always, "done") + + if restore.InputOptions.Archive != "" { + <-demuxFinished + return demuxErr + } return nil } diff --git a/src/mongo/gotools/mongorestore/mongorestore_archive_test.go b/src/mongo/gotools/mongorestore/mongorestore_archive_test.go new file mode 100644 index 00000000000..cbcefe40696 --- /dev/null +++ b/src/mongo/gotools/mongorestore/mongorestore_archive_test.go @@ -0,0 +1,95 @@ +package mongorestore + +import ( + "github.com/mongodb/mongo-tools/common/archive" + "github.com/mongodb/mongo-tools/common/db" + "github.com/mongodb/mongo-tools/common/log" + "github.com/mongodb/mongo-tools/common/options" + "github.com/mongodb/mongo-tools/common/testutil" + "github.com/mongodb/mongo-tools/common/util" + + . "github.com/smartystreets/goconvey/convey" + + "io" + "io/ioutil" + "os" + "testing" +) + +func init() { + // bump up the verbosity to make checking debug log output possible + log.SetVerbosity(&options.Verbosity{ + VLevel: 4, + }) +} + +var ( + testArchive = "testdata/test.bar.archive" +) + +func TestMongorestoreShortArchive(t *testing.T) { + Convey("With a test MongoRestore", t, func() { + ssl := testutil.GetSSLOptions() + auth := testutil.GetAuthOptions() + + testutil.VerifyTestType(t, testutil.IntegrationTestType) + toolOptions := &options.ToolOptions{ + Connection: &options.Connection{ + Host: testServer, + Port: testPort, + }, + Auth: &auth, + SSL: &ssl, + } + inputOptions := &InputOptions{ + Archive: testArchive, + } + outputOptions := &OutputOptions{ + NumParallelCollections: 1, + NumInsertionWorkers: 1, + WriteConcern: "majority", + Drop: true, + } + nsOptions := &NSOptions{} + provider, err := db.NewSessionProvider(*toolOptions) + if err != nil { + log.Logvf(log.Always, "error connecting to host: %v", err) + os.Exit(util.ExitError) + } + file, err := os.Open(testArchive) + So(file, ShouldNotBeNil) + So(err, ShouldBeNil) + + fi, err := file.Stat() + So(fi, ShouldNotBeNil) + So(err, ShouldBeNil) + + fileSize := fi.Size() + + for i := fileSize; i >= 0; i-- { + + log.Logvf(log.Always, "Restoring from the first %v bytes of a archive of size %v", i, fileSize) + + _, err = file.Seek(0, 0) + So(err, ShouldBeNil) + + restore := MongoRestore{ + ToolOptions: toolOptions, + OutputOptions: outputOptions, + InputOptions: inputOptions, + NSOptions: nsOptions, + SessionProvider: provider, + archive: &archive.Reader{ + Prelude: &archive.Prelude{}, + In: ioutil.NopCloser(io.LimitReader(file, i)), + }, + } + err = restore.Restore() + if i == fileSize { + So(err, ShouldBeNil) + } else { + So(err, ShouldNotBeNil) + } + } + }) +} diff --git a/src/mongo/gotools/mongorestore/testdata/test.bar.archive b/src/mongo/gotools/mongorestore/testdata/test.bar.archive Binary files differnew file mode 100644 index 00000000000..50f8f1c877f --- /dev/null +++ b/src/mongo/gotools/mongorestore/testdata/test.bar.archive diff --git a/src/mongo/gotools/mongostat/main/mongostat.go b/src/mongo/gotools/mongostat/main/mongostat.go index 8663248ceb6..97c79f0ca4f 100644 --- a/src/mongo/gotools/mongostat/main/mongostat.go +++ b/src/mongo/gotools/mongostat/main/mongostat.go @@ -229,6 +229,7 @@ func main() { // kick it off err = stat.Run() + formatter.Finish() if err != nil { log.Logvf(log.Always, "Failed: %v", err) os.Exit(util.ExitError) diff --git a/src/mongo/gotools/mongostat/stat_consumer/formatter.go b/src/mongo/gotools/mongostat/stat_consumer/formatter.go index 2fb1a4c5506..1acaa842e09 100644 --- a/src/mongo/gotools/mongostat/stat_consumer/formatter.go +++ b/src/mongo/gotools/mongostat/stat_consumer/formatter.go @@ -13,6 +13,8 @@ type LineFormatter interface { // IsFinished returns true iff the formatter cannot print any more data IsFinished() bool + // Finish() is called whem mongostat is shutting down so that the fomatter can clean up + Finish() } type limitableFormatter struct { diff --git a/src/mongo/gotools/mongostat/stat_consumer/grid_line_formatter.go b/src/mongo/gotools/mongostat/stat_consumer/grid_line_formatter.go index 53a6e2d4a37..7a03a21118c 100644 --- a/src/mongo/gotools/mongostat/stat_consumer/grid_line_formatter.go +++ b/src/mongo/gotools/mongostat/stat_consumer/grid_line_formatter.go @@ -40,6 +40,9 @@ func init() { // headerInterval is the number of chunks before the header is re-printed in GridLineFormatter const headerInterval = 10 +func (glf *GridLineFormatter) Finish() { +} + // FormatLines formats the StatLines as a grid func (glf *GridLineFormatter) FormatLines(lines []*line.StatLine, headerKeys []string, keyNames map[string]string) string { buf := &bytes.Buffer{} diff --git a/src/mongo/gotools/mongostat/stat_consumer/interactive_line_formatter.go b/src/mongo/gotools/mongostat/stat_consumer/interactive_line_formatter.go index 87cc08fef26..466e7c93d83 100644 --- a/src/mongo/gotools/mongostat/stat_consumer/interactive_line_formatter.go +++ b/src/mongo/gotools/mongostat/stat_consumer/interactive_line_formatter.go @@ -6,6 +6,7 @@ import ( "fmt" "sort" "strings" + "sync" "github.com/mongodb/mongo-tools/mongostat/stat_consumer/line" "github.com/nsf/termbox-go" @@ -19,6 +20,7 @@ type InteractiveLineFormatter struct { table []*column row, col int showHelp bool + sync.Mutex } func NewInteractiveLineFormatter(_ int64, includeHeader bool) LineFormatter { @@ -30,6 +32,7 @@ func NewInteractiveLineFormatter(_ int64, includeHeader bool) LineFormatter { fmt.Printf("Error setting up terminal UI: %v", err) panic("could not set up interactive terminal interface") } + go func() { for { ilf.handleEvent(termbox.PollEvent()) @@ -56,8 +59,15 @@ type cell struct { header bool } +func (ilf *InteractiveLineFormatter) Finish() { + termbox.Close() +} + // FormatLines formats the StatLines as a table in the terminal ui func (ilf *InteractiveLineFormatter) FormatLines(lines []*line.StatLine, headerKeys []string, keyNames map[string]string) string { + defer ilf.update() // so that it runs after the unlock, bnecause update locks again + ilf.Lock() + defer ilf.Unlock() // keep ordering consistent sort.Sort(line.StatLines(lines)) @@ -99,14 +109,16 @@ func (ilf *InteractiveLineFormatter) FormatLines(lines []*line.StatLine, headerK } } - ilf.update() return "" } func (ilf *InteractiveLineFormatter) handleEvent(ev termbox.Event) { + ilf.Lock() + defer ilf.Unlock() if ev.Type != termbox.EventKey { return } + currSelected := ilf.table[ilf.col].cells[ilf.row].selected switch { case ev.Key == termbox.KeyCtrlC: @@ -114,7 +126,6 @@ func (ilf *InteractiveLineFormatter) handleEvent(ev termbox.Event) { case ev.Key == termbox.KeyEsc: fallthrough case ev.Ch == 'q': - termbox.Close() // our max rowCount is set to 1; increment to exit ilf.increment() case ev.Key == termbox.KeyArrowRight: @@ -190,6 +201,8 @@ func writeString(x, y int, text string, fg, bg termbox.Attribute) { } func (ilf *InteractiveLineFormatter) update() { + ilf.Lock() + defer ilf.Unlock() termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) x := 0 for i, column := range ilf.table { diff --git a/src/mongo/gotools/mongostat/stat_consumer/json_line_formatter.go b/src/mongo/gotools/mongostat/stat_consumer/json_line_formatter.go index 0452072ad39..0e214f73e02 100644 --- a/src/mongo/gotools/mongostat/stat_consumer/json_line_formatter.go +++ b/src/mongo/gotools/mongostat/stat_consumer/json_line_formatter.go @@ -21,6 +21,8 @@ func NewJSONLineFormatter(maxRows int64, _ bool) LineFormatter { func init() { FormatterConstructors["json"] = NewJSONLineFormatter } +func (glf *JSONLineFormatter) Finish() { +} // FormatLines formats the StatLines as JSON func (jlf *JSONLineFormatter) FormatLines(lines []*line.StatLine, headerKeys []string, keyNames map[string]string) string { diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson.go index 7fb7f8cae48..c023b0638bb 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson.go @@ -51,6 +51,8 @@ import ( "time" ) +//go:generate go run bson_corpus_spec_test_generator.go + // -------------------------------------------------------------------------- // The public API. @@ -279,7 +281,7 @@ var nullBytes = []byte("null") func (id *ObjectId) UnmarshalJSON(data []byte) error { if len(data) > 0 && (data[0] == '{' || data[0] == 'O') { var v struct { - Id json.RawMessage `json:"$oid"` + Id json.RawMessage `json:"$oid"` Func struct { Id json.RawMessage } `json:"$oidFunc"` @@ -561,6 +563,9 @@ func Unmarshal(in []byte, out interface{}) (err error) { case reflect.Map: d := newDecoder(in) d.readDocTo(v) + if d.i < len(d.in) { + return errors.New("Document is corrupted") + } case reflect.Struct: return errors.New("Unmarshal can't deal with struct values. Use a pointer.") default: diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson_test.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson_test.go index 37451f9fdc2..11fdfae4185 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson_test.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/bson_test.go @@ -29,19 +29,16 @@ package bson_test import ( "encoding/binary" - "encoding/hex" "encoding/json" "encoding/xml" "errors" "net/url" "reflect" - "strings" "testing" "time" . "gopkg.in/check.v1" "gopkg.in/mgo.v2/bson" - "gopkg.in/yaml.v2" ) func TestAll(t *testing.T) { @@ -1581,65 +1578,6 @@ func (s *S) TestObjectIdJSONMarshaling(c *C) { } // -------------------------------------------------------------------------- -// Spec tests - -type specTest struct { - Description string - Documents []struct { - Decoded map[string]interface{} - Encoded string - DecodeOnly bool `yaml:"decodeOnly"` - Error interface{} - } -} - -func (s *S) TestSpecTests(c *C) { - for _, data := range specTests { - var test specTest - err := yaml.Unmarshal([]byte(data), &test) - c.Assert(err, IsNil) - - c.Logf("Running spec test set %q", test.Description) - - for _, doc := range test.Documents { - if doc.Error != nil { - continue - } - c.Logf("Ensuring %q decodes as %v", doc.Encoded, doc.Decoded) - var decoded map[string]interface{} - encoded, err := hex.DecodeString(doc.Encoded) - c.Assert(err, IsNil) - err = bson.Unmarshal(encoded, &decoded) - c.Assert(err, IsNil) - c.Assert(decoded, DeepEquals, doc.Decoded) - } - - for _, doc := range test.Documents { - if doc.DecodeOnly || doc.Error != nil { - continue - } - c.Logf("Ensuring %v encodes as %q", doc.Decoded, doc.Encoded) - encoded, err := bson.Marshal(doc.Decoded) - c.Assert(err, IsNil) - c.Assert(strings.ToUpper(hex.EncodeToString(encoded)), Equals, doc.Encoded) - } - - for _, doc := range test.Documents { - if doc.Error == nil { - continue - } - c.Logf("Ensuring %q errors when decoded: %s", doc.Encoded, doc.Error) - var decoded map[string]interface{} - encoded, err := hex.DecodeString(doc.Encoded) - c.Assert(err, IsNil) - err = bson.Unmarshal(encoded, &decoded) - c.Assert(err, NotNil) - c.Logf("Failed with: %v", err) - } - } -} - -// -------------------------------------------------------------------------- // ObjectId Text encoding.TextUnmarshaler. var textIdTests = []struct { diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/decode.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/decode.go index 7c2d8416afe..fd29da45071 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/decode.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/decode.go @@ -225,61 +225,65 @@ func (d *decoder) readDocTo(out reflect.Value) { panic("Unsupported document type for unmarshalling: " + out.Type().String()) } - end := int(d.readInt32()) - end += d.i - 4 - if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { - corrupted() - } - for d.in[d.i] != '\x00' { - kind := d.readByte() - name := d.readCStr() - if d.i >= end { + if outt == typeRaw { + d.skipDoc() + } else { + end := int(d.readInt32()) + end += d.i - 4 + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { corrupted() } + for d.in[d.i] != '\x00' { + kind := d.readByte() + name := d.readCStr() + if d.i >= end { + corrupted() + } - switch outk { - case reflect.Map: - e := reflect.New(elemType).Elem() - if d.readElemTo(e, kind) { - k := reflect.ValueOf(name) - if convertKey { - k = k.Convert(keyType) + switch outk { + case reflect.Map: + e := reflect.New(elemType).Elem() + if d.readElemTo(e, kind) { + k := reflect.ValueOf(name) + if convertKey { + k = k.Convert(keyType) + } + out.SetMapIndex(k, e) } - out.SetMapIndex(k, e) - } - case reflect.Struct: - if outt == typeRaw { - d.dropElem(kind) - } else { - if info, ok := fieldsMap[name]; ok { - if info.Inline == nil { - d.readElemTo(out.Field(info.Num), kind) + case reflect.Struct: + if outt == typeRaw { + d.dropElem(kind) + } else { + if info, ok := fieldsMap[name]; ok { + if info.Inline == nil { + d.readElemTo(out.Field(info.Num), kind) + } else { + d.readElemTo(out.FieldByIndex(info.Inline), kind) + } + } else if inlineMap.IsValid() { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + e := reflect.New(elemType).Elem() + if d.readElemTo(e, kind) { + inlineMap.SetMapIndex(reflect.ValueOf(name), e) + } } else { - d.readElemTo(out.FieldByIndex(info.Inline), kind) - } - } else if inlineMap.IsValid() { - if inlineMap.IsNil() { - inlineMap.Set(reflect.MakeMap(inlineMap.Type())) - } - e := reflect.New(elemType).Elem() - if d.readElemTo(e, kind) { - inlineMap.SetMapIndex(reflect.ValueOf(name), e) + d.dropElem(kind) } - } else { - d.dropElem(kind) } + case reflect.Slice: } - case reflect.Slice: - } - if d.i >= end { + if d.i >= end { + corrupted() + } + } + d.i++ // '\x00' + if d.i != end { corrupted() } } - d.i++ // '\x00' - if d.i != end { - corrupted() - } d.docType = docType if outt == typeRaw { @@ -431,7 +435,62 @@ func (d *decoder) readDocWith(f func(kind byte, name string)) { var blackHole = settableValueOf(struct{}{}) func (d *decoder) dropElem(kind byte) { - d.readElemTo(blackHole, kind) + switch kind { + case 0x01, 0x09, 0x11, 0x12: // double, utc datetime, timestamp, int64 + d.i += 8 + case 0x02, 0x0D, 0x0E: // string, javascript, symbol + l := int(d.readInt32()) + if l <= 0 || d.i+l >= len(d.in) || d.in[d.i+l-1] != 0x00 { + corrupted() + } + d.i += l + case 0x03, 0x04: // doc, array + d.skipDoc() + case 0x05: // binary + l := int(d.readInt32()) + k := d.readByte() + if k == 0x02 && l > 4 { + rl := int(d.readInt32()) + if rl != l-4 { + corrupted() + } + } + d.i += l + case 0x06: // undefined + case 0x07: // objectID + d.i += 12 + case 0x08: + k := d.readByte() + if k != 0x00 && k != 0x01 { + corrupted() + } + case 0x0A: // null + case 0x0B: // regex + d.readCStr() + d.readCStr() + case 0x0C: // dbpointer + d.dropElem(0x02) + d.i += 12 + case 0x0F: + start := d.i + l := int(d.readInt32()) + d.dropElem(0x02) // string + d.skipDoc() + if d.i != start+l { + corrupted() + } + case 0x10: // int32 + d.i += 4 + case 0x13: // decimal + d.i += 16 + case 0xFF, 0x7F: //min key, max key + default: + d.readElemTo(blackHole, kind) + } + + if d.i > len(d.in) { + corrupted() + } } // Attempt to decode an element from the document and put it into out. @@ -461,11 +520,11 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { case typeRawDocElem: out.Set(d.readRawDocElems(outt)) default: - d.readDocTo(blackHole) + d.skipDoc() } return true } - d.readDocTo(blackHole) + d.skipDoc() return true } @@ -529,9 +588,13 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { case 0x0E: // Symbol in = Symbol(d.readStr()) case 0x0F: // JavaScript with scope - d.i += 4 // Skip length + start := d.i + l := int(d.readInt32()) js := JavaScript{d.readStr(), make(M)} d.readDocTo(reflect.ValueOf(js.Scope)) + if d.i != start+l { + corrupted() + } in = js case 0x10: // Int32 in = int(d.readInt32()) @@ -748,6 +811,15 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { // -------------------------------------------------------------------------- // Parsers of basic types. +func (d *decoder) skipDoc() { + end := int(d.readInt32()) + end += d.i - 4 + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { + corrupted() + } + d.i = end +} + func (d *decoder) readRegEx() RegEx { re := RegEx{} re.Pattern = d.readCStr() @@ -759,11 +831,15 @@ func (d *decoder) readBinary() Binary { l := d.readInt32() b := Binary{} b.Kind = d.readByte() - b.Data = d.readBytes(l) - if b.Kind == 0x02 && len(b.Data) >= 4 { + if b.Kind == 0x02 && l > 4 { // Weird obsolete format with redundant length. - b.Data = b.Data[4:] + rl := d.readInt32() + if rl != l-4 { + corrupted() + } + l = rl } + b.Data = d.readBytes(l) return b } diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/encode.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/encode.go index add39e865dd..25f1adc6303 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/encode.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/encode.go @@ -33,6 +33,7 @@ import ( "math" "net/url" "reflect" + "sort" "strconv" "time" ) @@ -419,7 +420,9 @@ func (e *encoder) addElem(name string, v reflect.Value, minSize bool) { case RegEx: e.addElemName(0x0B, name) e.addCStr(s.Pattern) - e.addCStr(s.Options) + options := runes(s.Options) + sort.Sort(options) + e.addCStr(string(options)) case JavaScript: if s.Scope == nil { @@ -455,6 +458,14 @@ func (e *encoder) addElem(name string, v reflect.Value, minSize bool) { } } +// ------------- +// Helper method for sorting regex options +type runes []rune + +func (a runes) Len() int { return len(a) } +func (a runes) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a runes) Less(i, j int) bool { return a[i] < a[j] } + // -------------------------------------------------------------------------- // Marshaling of base types. diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/specdata/update.sh b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/specdata/update.sh index 1efd3d3b66d..7b1141c105e 100755 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/specdata/update.sh +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/specdata/update.sh @@ -1,27 +1,9 @@ -#!/bin/sh +#/bin/sh set -e -if [ ! -d specifications ]; then - git clone -b bson git@github.com:jyemin/specifications -fi +rm -rf specifications -TESTFILE="../specdata_test.go" +git clone git@github.com:mongodb/specifications -cat <<END > $TESTFILE -package bson_test - -var specTests = []string{ -END - -for file in specifications/source/bson/tests/*.yml; do - ( - echo '`' - cat $file - echo -n '`,' - ) >> $TESTFILE -done - -echo '}' >> $TESTFILE - -gofmt -w $TESTFILE +go generate ../
\ No newline at end of file diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/specdata_test.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/specdata_test.go deleted file mode 100644 index 513f9b209c7..00000000000 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/bson/specdata_test.go +++ /dev/null @@ -1,241 +0,0 @@ -package bson_test - -var specTests = []string{ - ` ---- -description: "Array type" -documents: - - - decoded: - a : [] - encoded: 0D000000046100050000000000 - - - decoded: - a: [10] - encoded: 140000000461000C0000001030000A0000000000 - - - # Decode an array that uses an empty string as the key - decodeOnly : true - decoded: - a: [10] - encoded: 130000000461000B00000010000A0000000000 - - - # Decode an array that uses a non-numeric string as the key - decodeOnly : true - decoded: - a: [10] - encoded: 150000000461000D000000106162000A0000000000 - - -`, ` ---- -description: "Boolean type" -documents: - - - encoded: "090000000862000100" - decoded: { "b" : true } - - - encoded: "090000000862000000" - decoded: { "b" : false } - - - `, ` ---- -description: "Corrupted BSON" -documents: - - - encoded: "09000000016600" - error: "truncated double" - - - encoded: "09000000026600" - error: "truncated string" - - - encoded: "09000000036600" - error: "truncated document" - - - encoded: "09000000046600" - error: "truncated array" - - - encoded: "09000000056600" - error: "truncated binary" - - - encoded: "09000000076600" - error: "truncated objectid" - - - encoded: "09000000086600" - error: "truncated boolean" - - - encoded: "09000000096600" - error: "truncated date" - - - encoded: "090000000b6600" - error: "truncated regex" - - - encoded: "090000000c6600" - error: "truncated db pointer" - - - encoded: "0C0000000d6600" - error: "truncated javascript" - - - encoded: "0C0000000e6600" - error: "truncated symbol" - - - encoded: "0C0000000f6600" - error: "truncated javascript with scope" - - - encoded: "0C000000106600" - error: "truncated int32" - - - encoded: "0C000000116600" - error: "truncated timestamp" - - - encoded: "0C000000126600" - error: "truncated int64" - - - encoded: "0400000000" - error: basic - - - encoded: "0500000001" - error: basic - - - encoded: "05000000" - error: basic - - - encoded: "0700000002610078563412" - error: basic - - - encoded: "090000001061000500" - error: basic - - - encoded: "00000000000000000000" - error: basic - - - encoded: "1300000002666f6f00040000006261720000" - error: "basic" - - - encoded: "1800000003666f6f000f0000001062617200ffffff7f0000" - error: basic - - - encoded: "1500000003666f6f000c0000000862617200010000" - error: basic - - - encoded: "1c00000003666f6f001200000002626172000500000062617a000000" - error: basic - - - encoded: "1000000002610004000000616263ff00" - error: string is not null-terminated - - - encoded: "0c0000000200000000000000" - error: bad_string_length - - - encoded: "120000000200ffffffff666f6f6261720000" - error: bad_string_length - - - encoded: "0c0000000e00000000000000" - error: bad_string_length - - - encoded: "120000000e00ffffffff666f6f6261720000" - error: bad_string_length - - - encoded: "180000000c00fa5bd841d6585d9900" - error: "" - - - encoded: "1e0000000c00ffffffff666f6f626172005259b56afa5bd841d6585d9900" - error: bad_string_length - - - encoded: "0c0000000d00000000000000" - error: bad_string_length - - - encoded: "0c0000000d00ffffffff0000" - error: bad_string_length - - - encoded: "1c0000000f001500000000000000000c000000020001000000000000" - error: bad_string_length - - - encoded: "1c0000000f0015000000ffffffff000c000000020001000000000000" - error: bad_string_length - - - encoded: "1c0000000f001500000001000000000c000000020000000000000000" - error: bad_string_length - - - encoded: "1c0000000f001500000001000000000c0000000200ffffffff000000" - error: bad_string_length - - - encoded: "0E00000008616263646566676869707172737475" - error: "Run-on CString" - - - encoded: "0100000000" - error: "An object size that's too small to even include the object size, but is correctly encoded, along with a correct EOO (and no data)" - - - encoded: "1a0000000e74657374000c00000068656c6c6f20776f726c6400000500000000" - error: "One object, but with object size listed smaller than it is in the data" - - - encoded: "05000000" - error: "One object, missing the EOO at the end" - - - encoded: "0500000001" - error: "One object, sized correctly, with a spot for an EOO, but the EOO is 0x01" - - - encoded: "05000000ff" - error: "One object, sized correctly, with a spot for an EOO, but the EOO is 0xff" - - - encoded: "0500000070" - error: "One object, sized correctly, with a spot for an EOO, but the EOO is 0x70" - - - encoded: "07000000000000" - error: "Invalid BSON type low range" - - - encoded: "07000000800000" - error: "Invalid BSON type high range" - - - encoded: "090000000862000200" - error: "Invalid boolean value of 2" - - - encoded: "09000000086200ff00" - error: "Invalid boolean value of -1" - `, ` ---- -description: "Int32 type" -documents: - - - decoded: - i: -2147483648 - encoded: 0C0000001069000000008000 - - - decoded: - i: 2147483647 - encoded: 0C000000106900FFFFFF7F00 - - - decoded: - i: -1 - encoded: 0C000000106900FFFFFFFF00 - - - decoded: - i: 0 - encoded: 0C0000001069000000000000 - - - decoded: - i: 1 - encoded: 0C0000001069000100000000 - -`, ` ---- -description: "String type" -documents: - - - decoded: - s : "" - encoded: 0D000000027300010000000000 - - - decoded: - s: "a" - encoded: 0E00000002730002000000610000 - - - decoded: - s: "This is a string" - encoded: 1D0000000273001100000054686973206973206120737472696E670000 - - - decoded: - s: "κόσμε" - encoded: 180000000273000C000000CEBAE1BDB9CF83CEBCCEB50000 -`} diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/decode.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/decode.go index ce7c7d2493d..2171d91a711 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/decode.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/decode.go @@ -773,7 +773,7 @@ func (d *decodeState) isNull(off int) bool { // name consumes a const or function from d.data[d.off-1:], decoding into the value v. // the first byte of the function name has been read already. func (d *decodeState) name(v reflect.Value) { - if d.isNull(d.off-1) { + if d.isNull(d.off - 1) { d.literal(v) return } @@ -1036,7 +1036,7 @@ func (d *decodeState) keyed() (interface{}, bool) { break } - name := d.data[d.off-1+start : d.off-1+end] + name := bytes.Trim(d.data[d.off-1+start:d.off-1+end], " \n\t") var key []byte var ok bool @@ -1076,9 +1076,9 @@ func (d *decodeState) storeKeyed(v reflect.Value) bool { } var ( - trueBytes = []byte("true") + trueBytes = []byte("true") falseBytes = []byte("false") - nullBytes = []byte("null") + nullBytes = []byte("null") ) func (d *decodeState) storeValue(v reflect.Value, from interface{}) { diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/extension_test.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/extension_test.go index 8c228189724..c5db5cf559f 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/extension_test.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/extension_test.go @@ -131,6 +131,9 @@ var extDecodeTests = []extDecodeTest{ {in: `Const1`, ptr: new(interface{}), out: const1}, {in: `{"c": Const1}`, ptr: new(struct{ C *const1Type }), out: struct{ C *const1Type }{const1}}, + // Space after quoted key + {in: `{"c" : Const1}`, ptr: new(struct{ C *const1Type }), out: struct{ C *const1Type }{const1}}, + // Keyed documents {in: `{"v": {"$key1": 1}}`, ptr: new(interface{}), out: map[string]interface{}{"v": keyed(`{"$key1": 1}`)}}, {in: `{"k": {"$key1": 1}}`, ptr: new(keyedType), out: keyedType{K: keyed(`{"$key1": 1}`)}}, diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/stream_test.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/stream_test.go index 0abdf7b5654..4ebeaba961c 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/stream_test.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/internal/json/stream_test.go @@ -131,7 +131,7 @@ func TestDecoder(t *testing.T) { for _, c := range nlines(streamEncoded, i) { // That's stupid isn't it!? nulltrue!?!? :/ //if c != '\n' { - buf.WriteRune(c) + buf.WriteRune(c) //} } out := make([]interface{}, i) diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session.go index 12ca8f2ac37..108cdae8848 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session.go @@ -279,44 +279,81 @@ func ParseURL(url string) (*DialInfo, error) { source := "" setName := "" poolLimit := 0 - for k, v := range uinfo.options { - switch k { + readPreferenceMode := Primary + var readPreferenceTagSets []bson.D + for _, opt := range uinfo.options { + switch opt.key { case "authSource": - source = v + source = opt.value case "authMechanism": - mechanism = v + mechanism = opt.value case "gssapiServiceName": - service = v + service = opt.value case "replicaSet": - setName = v + setName = opt.value case "maxPoolSize": - poolLimit, err = strconv.Atoi(v) + poolLimit, err = strconv.Atoi(opt.value) if err != nil { - return nil, errors.New("bad value for maxPoolSize: " + v) + return nil, errors.New("bad value for maxPoolSize: " + opt.value) } + case "readPreference": + switch opt.value { + case "nearest": + readPreferenceMode = Nearest + case "primary": + readPreferenceMode = Primary + case "primaryPreferred": + readPreferenceMode = PrimaryPreferred + case "secondary": + readPreferenceMode = Secondary + case "secondaryPreferred": + readPreferenceMode = SecondaryPreferred + default: + return nil, errors.New("bad value for readPreference: " + opt.value) + } + case "readPreferenceTags": + tags := strings.Split(opt.value, ",") + var doc bson.D + for _, tag := range tags { + kvp := strings.Split(tag, ":") + if len(kvp) != 2 { + return nil, errors.New("bad value for readPreferenceTags: " + opt.value) + } + doc = append(doc, bson.DocElem{Name: strings.TrimSpace(kvp[0]), Value: strings.TrimSpace(kvp[1])}) + } + readPreferenceTagSets = append(readPreferenceTagSets, doc) case "connect": - if v == "direct" { + if opt.value == "direct" { direct = true break } - if v == "replicaSet" { + if opt.value == "replicaSet" { break } fallthrough default: - return nil, errors.New("unsupported connection URL option: " + k + "=" + v) + return nil, errors.New("unsupported connection URL option: " + opt.key + "=" + opt.value) } } + + if readPreferenceMode == Primary && len(readPreferenceTagSets) > 0 { + return nil, errors.New("readPreferenceTagSet may not be specified when readPreference is primary") + } + info := DialInfo{ - Addrs: uinfo.addrs, - Direct: direct, - Database: uinfo.db, - Username: uinfo.user, - Password: uinfo.pass, - Mechanism: mechanism, - Service: service, - Source: source, - PoolLimit: poolLimit, + Addrs: uinfo.addrs, + Direct: direct, + Database: uinfo.db, + Username: uinfo.user, + Password: uinfo.pass, + Mechanism: mechanism, + Service: service, + Source: source, + PoolLimit: poolLimit, + ReadPreference: &ReadPreference{ + Mode: readPreferenceMode, + TagSets: readPreferenceTagSets, + }, ReplicaSetName: setName, } return &info, nil @@ -384,6 +421,10 @@ type DialInfo struct { // See Session.SetPoolLimit for details. PoolLimit int + // ReadPreference defines the manner in which servers are chosen. See + // Session.SetMode and Session.SelectServers. + ReadPreference *ReadPreference + // DialServer optionally specifies the dial function for establishing // connections with the MongoDB servers. DialServer func(addr *ServerAddr) (net.Conn, error) @@ -392,6 +433,15 @@ type DialInfo struct { Dial func(addr net.Addr) (net.Conn, error) } +// ReadPreference defines the manner in which servers are chosen. +type ReadPreference struct { + // Mode determines the consistency of results. See Session.SetMode. + Mode Mode + + // TagSets indicates which servers are allowed to be used. See Session.SelectServers. + TagSets []bson.D +} + // mgo.v3: Drop DialInfo.Dial. // ServerAddr represents the address for establishing a connection to an @@ -464,7 +514,14 @@ func DialWithInfo(info *DialInfo) (*Session, error) { session.Close() return nil, err } - session.SetMode(Strong, true) + + if info.ReadPreference != nil { + session.SelectServers(info.ReadPreference.TagSets...) + session.SetMode(info.ReadPreference.Mode, true) + } else { + session.SetMode(Strong, true) + } + return session, nil } @@ -477,21 +534,26 @@ type urlInfo struct { user string pass string db string - options map[string]string + options []urlInfoOption +} + +type urlInfoOption struct { + key string + value string } func extractURL(s string) (*urlInfo, error) { if strings.HasPrefix(s, "mongodb://") { s = s[10:] } - info := &urlInfo{options: make(map[string]string)} + info := &urlInfo{} if c := strings.Index(s, "?"); c != -1 { for _, pair := range strings.FieldsFunc(s[c+1:], isOptSep) { l := strings.SplitN(pair, "=", 2) if len(l) != 2 || l[0] == "" || l[1] == "" { return nil, errors.New("connection option must be key=value: " + pair) } - info.options[l[0]] = l[1] + info.options = append(info.options, urlInfoOption{key: l[0], value: l[1]}) } s = s[:c] } @@ -569,6 +631,17 @@ func (s *Session) LiveServers() (addrs []string) { return addrs } +// ReadableServer returns a server address which is suitable for reading +// according to the current session. +func (s *Session) ReadableServer() (string, error) { + socket, err := s.acquireSocket(true) + if err != nil { + return "", err + } + defer socket.Release() + return socket.server.Addr, nil +} + // DB returns a value representing the named database. If name // is empty, the database name provided in the dialed URL is // used instead. If that is also empty, "test" is used as a @@ -1096,6 +1169,14 @@ type Collation struct { // distinguished at strength > 3). Defaults to "non-ignorable". Alternate string `bson:"alternate,omitempty"` + // MaxVariable defines which characters are affected when the value for Alternate is + // "shifted". It may be set to "punct" to affect punctuation or spaces, or "space" to + // affect only spaces. + MaxVariable string `bson:"maxVariable,omitempty"` + + // Normalization defines whether text is normalized into Unicode NFD. + Normalization bool `bson:"normalization,omitempty"` + // Backwards defines whether to have secondary differences considered in reverse order, // as done in the French language. Backwards bool `bson:"backwards,omitempty"` @@ -2299,6 +2380,11 @@ func (c *Collection) NewIter(session *Session, firstBatch []bson.Raw, cursorId i timeout: -1, err: err, } + + if socket.ServerInfo().MaxWireVersion >= 4 && c.FullName != "admin.$cmd" { + iter.findCmd = true + } + iter.gotReply.L = &iter.m for _, doc := range firstBatch { iter.docData.Push(doc.Data) @@ -2858,6 +2944,14 @@ func (q *Query) Sort(fields ...string) *Query { return q } +func (q *Query) Collation(collation *Collation) *Query { + q.m.Lock() + q.op.options.Collation = collation + q.op.hasOptions = true + q.m.Unlock() + return q +} + // Explain returns a number of details about how the MongoDB server would // execute the requested query, such as the number of objects examined, // the number of times the read lock was yielded to allow writes to go in, @@ -3161,6 +3255,7 @@ func prepareFindOp(socket *mongoSocket, op *queryOp, limit int32) bool { Comment: op.options.Comment, Snapshot: op.options.Snapshot, OplogReplay: op.flags&flagLogReplay != 0, + Collation: op.options.Collation, } if op.limit < 0 { find.BatchSize = -op.limit @@ -3222,6 +3317,7 @@ type findCmd struct { OplogReplay bool `bson:"oplogReplay,omitempty"` NoCursorTimeout bool `bson:"noCursorTimeout,omitempty"` AllowPartialResults bool `bson:"allowPartialResults,omitempty"` + Collation *Collation `bson:"collation,omitempty"` } // getMoreCmd holds the command used for requesting more query results on MongoDB 3.2+. diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session_test.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session_test.go index a89279d38b1..314ace5cd5c 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session_test.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/session_test.go @@ -132,6 +132,93 @@ func (s *S) TestURLParsing(c *C) { } } +func (s *S) TestURLReadPreference(c *C) { + type test struct { + url string + mode mgo.Mode + } + + tests := []test{ + {"localhost:40001?readPreference=primary", mgo.Primary}, + {"localhost:40001?readPreference=primaryPreferred", mgo.PrimaryPreferred}, + {"localhost:40001?readPreference=secondary", mgo.Secondary}, + {"localhost:40001?readPreference=secondaryPreferred", mgo.SecondaryPreferred}, + {"localhost:40001?readPreference=nearest", mgo.Nearest}, + } + + for _, test := range tests { + info, err := mgo.ParseURL(test.url) + c.Assert(err, IsNil) + c.Assert(info.ReadPreference, NotNil) + c.Assert(info.ReadPreference.Mode, Equals, test.mode) + } +} + +func (s *S) TestURLInvalidReadPreference(c *C) { + urls := []string{ + "localhost:40001?readPreference=foo", + "localhost:40001?readPreference=primarypreferred", + } + for _, url := range urls { + _, err := mgo.ParseURL(url) + c.Assert(err, NotNil) + } +} + +func (s *S) TestURLReadPreferenceTags(c *C) { + type test struct { + url string + tagSets []bson.D + } + + tests := []test{ + {"localhost:40001?readPreference=secondary&readPreferenceTags=dc:ny,rack:1", []bson.D{{{"dc", "ny"}, {"rack", "1"}}}}, + {"localhost:40001?readPreference=secondary&readPreferenceTags= dc : ny , rack : 1 ", []bson.D{{{"dc", "ny"}, {"rack", "1"}}}}, + {"localhost:40001?readPreference=secondary&readPreferenceTags=dc:ny", []bson.D{{{"dc", "ny"}}}}, + {"localhost:40001?readPreference=secondary&readPreferenceTags=rack:1&readPreferenceTags=dc:ny", []bson.D{{{"rack", "1"}}, {{"dc", "ny"}}}}, + } + + for _, test := range tests { + info, err := mgo.ParseURL(test.url) + c.Assert(err, IsNil) + c.Assert(info.ReadPreference, NotNil) + c.Assert(info.ReadPreference.TagSets, DeepEquals, test.tagSets) + } +} + +func (s *S) TestURLInvalidReadPreferenceTags(c *C) { + urls := []string{ + "localhost:40001?readPreference=secondary&readPreferenceTags=dc", + "localhost:40001?readPreference=secondary&readPreferenceTags=dc:ny,rack", + "localhost:40001?readPreference=secondary&readPreferenceTags=dc,rack", + "localhost:40001?readPreference=primary&readPreferenceTags=dc:ny", + } + for _, url := range urls { + _, err := mgo.ParseURL(url) + c.Assert(err, NotNil) + } +} + +func (s *S) TestReadableServer(c *C) { + session, err := mgo.Dial("localhost:40011,localhost:40012,localhost:40013") + c.Assert(err, IsNil) + defer session.Close() + + session.SetMode(mgo.Primary, true) + primary_address, err := session.ReadableServer() + c.Assert(err, IsNil) + c.Assert(primary_address, Equals, "localhost:40011") + + session.SetMode(mgo.Secondary, true) + secondary_address, err := session.ReadableServer() + c.Assert(err, IsNil) + + valid_addresses := []string{"localhost:40012", "localhost:40013"} + if secondary_address != valid_addresses[0] && secondary_address != valid_addresses[1] { + c.Fatalf("secondary_address should be in %v, not: %v", valid_addresses, secondary_address) + } +} + func (s *S) TestInsertFindOne(c *C) { session, err := mgo.Dial("localhost:40001") c.Assert(err, IsNil) @@ -4159,11 +4246,11 @@ func (s *S) TestBypassValidation(c *C) { func (s *S) TestVersionAtLeast(c *C) { tests := [][][]int{ - {{3,2,1}, {3,2,0}}, - {{3,2,1}, {3,2}}, - {{3,2,1}, {2,5,5,5}}, - {{3,2,1}, {2,5,5}}, - {{3,2,1}, {2,5}}, + {{3, 2, 1}, {3, 2, 0}}, + {{3, 2, 1}, {3, 2}}, + {{3, 2, 1}, {2, 5, 5, 5}}, + {{3, 2, 1}, {2, 5, 5}}, + {{3, 2, 1}, {2, 5}}, } for _, pair := range tests { bi := mgo.BuildInfo{VersionArray: pair[0]} @@ -4180,6 +4267,48 @@ func (s *S) TestVersionAtLeast(c *C) { } } +func (s *S) TestCollationQueries(c *C) { + if !s.versionAtLeast(3, 3, 12) { + c.Skip("collations being released with 3.4") + } + session, err := mgo.Dial("localhost:40001") + c.Assert(err, IsNil) + defer session.Close() + + docsToInsert := []bson.M{ + {"text_number": "010"}, + {"text_number": "2"}, + {"text_number": "10"}, + } + + coll := session.DB("mydb").C("mycoll") + for _, doc := range docsToInsert { + err = coll.Insert(doc) + c.Assert(err, IsNil) + } + + collation := &mgo.Collation{ + Locale: "en", + NumericOrdering: true, + } + + err = coll.EnsureIndex(mgo.Index{ + Key: []string{"text_number"}, + Collation: collation, + }) + c.Assert(err, IsNil) + + iter := coll.Find(nil).Sort("text_number").Collation(collation).Iter() + defer iter.Close() + for _, expectedRes := range []string{"2", "010", "10"} { + res := make(bson.M) + found := iter.Next(&res) + c.Assert(iter.Err(), IsNil) + c.Assert(found, Equals, true) + c.Assert(res["text_number"], Equals, expectedRes) + } +} + // -------------------------------------------------------------------------- // Some benchmarks that require a running database. diff --git a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/socket.go b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/socket.go index 8891dd5d734..f4941269217 100644 --- a/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/socket.go +++ b/src/mongo/gotools/vendor/src/gopkg.in/mgo.v2/socket.go @@ -91,6 +91,7 @@ type queryWrapper struct { MaxScan int "$maxScan,omitempty" MaxTimeMS int "$maxTimeMS,omitempty" Comment string "$comment,omitempty" + Collation *Collation "$collation,omitempty" } func (op *queryOp) finalQuery(socket *mongoSocket) interface{} { |