mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
4237 lines
103 KiB
Go
4237 lines
103 KiB
Go
// mgo - MongoDB driver for Go
|
|
//
|
|
// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
|
// list of conditions and the following disclaimer.
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package mgo_test
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
. "gopkg.in/check.v1"
|
|
"gopkg.in/mgo.v2"
|
|
"gopkg.in/mgo.v2/bson"
|
|
)
|
|
|
|
func (s *S) TestRunString(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
result := struct{ Ok int }{}
|
|
err = session.Run("ping", &result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.Ok, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestRunValue(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
result := struct{ Ok int }{}
|
|
err = session.Run(M{"ping": 1}, &result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.Ok, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestPing(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
// Just ensure the nonce has been received.
|
|
result := struct{}{}
|
|
err = session.Run("ping", &result)
|
|
|
|
mgo.ResetStats()
|
|
|
|
err = session.Ping()
|
|
c.Assert(err, IsNil)
|
|
|
|
// Pretty boring.
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 1)
|
|
c.Assert(stats.ReceivedOps, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestDialIPAddress(c *C) {
|
|
session, err := mgo.Dial("127.0.0.1:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
if os.Getenv("NOIPV6") != "1" {
|
|
session, err = mgo.Dial("[::1%]:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
}
|
|
}
|
|
|
|
func (s *S) TestURLSingle(c *C) {
|
|
session, err := mgo.Dial("mongodb://localhost:40001/")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
result := struct{ Ok int }{}
|
|
err = session.Run("ping", &result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.Ok, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestURLMany(c *C) {
|
|
session, err := mgo.Dial("mongodb://localhost:40011,localhost:40012/")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
result := struct{ Ok int }{}
|
|
err = session.Run("ping", &result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.Ok, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestURLParsing(c *C) {
|
|
urls := []string{
|
|
"localhost:40001?foo=1&bar=2",
|
|
"localhost:40001?foo=1;bar=2",
|
|
}
|
|
for _, url := range urls {
|
|
session, err := mgo.Dial(url)
|
|
if session != nil {
|
|
session.Close()
|
|
}
|
|
c.Assert(err, ErrorMatches, "unsupported connection URL option: (foo=1|bar=2)")
|
|
}
|
|
}
|
|
|
|
func (s *S) TestInsertFindOne(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Insert(M{"a": 1, "b": 2})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"a": 1, "b": 3})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := struct{ A, B int }{}
|
|
|
|
err = coll.Find(M{"a": 1}).Sort("b").One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.A, Equals, 1)
|
|
c.Assert(result.B, Equals, 2)
|
|
|
|
err = coll.Find(M{"a": 1}).Sort("-b").One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.A, Equals, 1)
|
|
c.Assert(result.B, Equals, 3)
|
|
}
|
|
|
|
func (s *S) TestInsertFindOneNil(c *C) {
|
|
session, err := mgo.Dial("localhost:40002")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Find(nil).One(nil)
|
|
c.Assert(err, ErrorMatches, "unauthorized.*|not authorized.*")
|
|
}
|
|
|
|
func (s *S) TestInsertFindOneMap(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Insert(M{"a": 1, "b": 2})
|
|
c.Assert(err, IsNil)
|
|
result := make(M)
|
|
err = coll.Find(M{"a": 1}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["a"], Equals, 1)
|
|
c.Assert(result["b"], Equals, 2)
|
|
}
|
|
|
|
func (s *S) TestInsertFindAll(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Insert(M{"a": 1, "b": 2})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"a": 3, "b": 4})
|
|
c.Assert(err, IsNil)
|
|
|
|
type R struct{ A, B int }
|
|
var result []R
|
|
|
|
assertResult := func() {
|
|
c.Assert(len(result), Equals, 2)
|
|
c.Assert(result[0].A, Equals, 1)
|
|
c.Assert(result[0].B, Equals, 2)
|
|
c.Assert(result[1].A, Equals, 3)
|
|
c.Assert(result[1].B, Equals, 4)
|
|
}
|
|
|
|
// nil slice
|
|
err = coll.Find(nil).Sort("a").All(&result)
|
|
c.Assert(err, IsNil)
|
|
assertResult()
|
|
|
|
// Previously allocated slice
|
|
allocd := make([]R, 5)
|
|
result = allocd
|
|
err = coll.Find(nil).Sort("a").All(&result)
|
|
c.Assert(err, IsNil)
|
|
assertResult()
|
|
|
|
// Ensure result is backed by the originally allocated array
|
|
c.Assert(&result[0], Equals, &allocd[0])
|
|
|
|
// Non-pointer slice error
|
|
f := func() { coll.Find(nil).All(result) }
|
|
c.Assert(f, Panics, "result argument must be a slice address")
|
|
|
|
// Non-slice error
|
|
f = func() { coll.Find(nil).All(new(int)) }
|
|
c.Assert(f, Panics, "result argument must be a slice address")
|
|
}
|
|
|
|
func (s *S) TestFindRef(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
db1 := session.DB("db1")
|
|
db1col1 := db1.C("col1")
|
|
|
|
db2 := session.DB("db2")
|
|
db2col1 := db2.C("col1")
|
|
|
|
err = db1col1.Insert(M{"_id": 1, "n": 1})
|
|
c.Assert(err, IsNil)
|
|
err = db1col1.Insert(M{"_id": 2, "n": 2})
|
|
c.Assert(err, IsNil)
|
|
err = db2col1.Insert(M{"_id": 2, "n": 3})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := struct{ N int }{}
|
|
|
|
ref1 := &mgo.DBRef{Collection: "col1", Id: 1}
|
|
ref2 := &mgo.DBRef{Collection: "col1", Id: 2, Database: "db2"}
|
|
|
|
err = db1.FindRef(ref1).One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 1)
|
|
|
|
err = db1.FindRef(ref2).One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 3)
|
|
|
|
err = db2.FindRef(ref1).One(&result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = db2.FindRef(ref2).One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 3)
|
|
|
|
err = session.FindRef(ref2).One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 3)
|
|
|
|
f := func() { session.FindRef(ref1).One(&result) }
|
|
c.Assert(f, PanicMatches, "Can't resolve database for &mgo.DBRef{Collection:\"col1\", Id:1, Database:\"\"}")
|
|
}
|
|
|
|
func (s *S) TestDatabaseAndCollectionNames(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
db1 := session.DB("db1")
|
|
db1col1 := db1.C("col1")
|
|
db1col2 := db1.C("col2")
|
|
|
|
db2 := session.DB("db2")
|
|
db2col1 := db2.C("col3")
|
|
|
|
err = db1col1.Insert(M{"_id": 1})
|
|
c.Assert(err, IsNil)
|
|
err = db1col2.Insert(M{"_id": 1})
|
|
c.Assert(err, IsNil)
|
|
err = db2col1.Insert(M{"_id": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
names, err := session.DatabaseNames()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(filterDBs(names), DeepEquals, []string{"db1", "db2"})
|
|
|
|
// Try to exercise cursor logic. 2.8.0-rc3 still ignores this.
|
|
session.SetBatch(2)
|
|
|
|
names, err = db1.CollectionNames()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(names, DeepEquals, []string{"col1", "col2", "system.indexes"})
|
|
|
|
names, err = db2.CollectionNames()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(names, DeepEquals, []string{"col3", "system.indexes"})
|
|
}
|
|
|
|
func (s *S) TestSelect(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
coll.Insert(M{"a": 1, "b": 2})
|
|
|
|
result := struct{ A, B int }{}
|
|
|
|
err = coll.Find(M{"a": 1}).Select(M{"b": 1}).One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.A, Equals, 0)
|
|
c.Assert(result.B, Equals, 2)
|
|
}
|
|
|
|
func (s *S) TestInlineMap(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
var v, result1 struct {
|
|
A int
|
|
M map[string]int ",inline"
|
|
}
|
|
|
|
v.A = 1
|
|
v.M = map[string]int{"b": 2}
|
|
err = coll.Insert(v)
|
|
c.Assert(err, IsNil)
|
|
|
|
noId := M{"_id": 0}
|
|
|
|
err = coll.Find(nil).Select(noId).One(&result1)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result1.A, Equals, 1)
|
|
c.Assert(result1.M, DeepEquals, map[string]int{"b": 2})
|
|
|
|
var result2 M
|
|
err = coll.Find(nil).Select(noId).One(&result2)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result2, DeepEquals, M{"a": 1, "b": 2})
|
|
|
|
}
|
|
|
|
func (s *S) TestUpdate(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"k": n, "n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
// No changes is a no-op and shouldn't return an error.
|
|
err = coll.Update(M{"k": 42}, M{"$set": M{"n": 42}})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Update(M{"k": 42}, M{"$inc": M{"n": 1}})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := make(M)
|
|
err = coll.Find(M{"k": 42}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 43)
|
|
|
|
err = coll.Update(M{"k": 47}, M{"k": 47, "n": 47})
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.Find(M{"k": 47}).One(result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
}
|
|
|
|
func (s *S) TestUpdateId(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"_id": n, "n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
err = coll.UpdateId(42, M{"$inc": M{"n": 1}})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := make(M)
|
|
err = coll.FindId(42).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 43)
|
|
|
|
err = coll.UpdateId(47, M{"k": 47, "n": 47})
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.FindId(47).One(result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
}
|
|
|
|
func (s *S) TestUpdateNil(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"k": 42, "n": 42})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Update(nil, M{"$inc": M{"n": 1}})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := make(M)
|
|
err = coll.Find(M{"k": 42}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 43)
|
|
|
|
err = coll.Insert(M{"k": 45, "n": 45})
|
|
c.Assert(err, IsNil)
|
|
_, err = coll.UpdateAll(nil, M{"$inc": M{"n": 1}})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Find(M{"k": 42}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 44)
|
|
err = coll.Find(M{"k": 45}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 46)
|
|
}
|
|
|
|
func (s *S) TestUpsert(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(bson.D{{"k", n}, {"n", n}})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
info, err := coll.Upsert(M{"k": 42}, bson.D{{"k", 42}, {"n", 24}})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 1)
|
|
c.Assert(info.Matched, Equals, 1)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
result := M{}
|
|
err = coll.Find(M{"k": 42}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 24)
|
|
|
|
// Match but do not change.
|
|
info, err = coll.Upsert(M{"k": 42}, bson.D{{"k", 42}, {"n", 24}})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 1) // On 2.6+ this feels like a server mistake.
|
|
c.Assert(info.Matched, Equals, 1)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
// Insert with internally created id.
|
|
info, err = coll.Upsert(M{"k": 47}, M{"k": 47, "n": 47})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
c.Assert(info.Matched, Equals, 0)
|
|
c.Assert(info.UpsertedId, NotNil)
|
|
|
|
err = coll.Find(M{"k": 47}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 47)
|
|
|
|
result = M{}
|
|
err = coll.Find(M{"_id": info.UpsertedId}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 47)
|
|
|
|
// Insert with provided id.
|
|
info, err = coll.Upsert(M{"k": 48}, M{"k": 48, "n": 48, "_id": 48})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
c.Assert(info.Matched, Equals, 0)
|
|
if s.versionAtLeast(2, 6) {
|
|
c.Assert(info.UpsertedId, Equals, 48)
|
|
} else {
|
|
c.Assert(info.UpsertedId, IsNil) // Unfortunate, but that's what Mongo gave us.
|
|
}
|
|
|
|
err = coll.Find(M{"k": 48}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 48)
|
|
}
|
|
|
|
func (s *S) TestUpsertId(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"_id": n, "n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
info, err := coll.UpsertId(42, M{"n": 24})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 1)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
result := M{}
|
|
err = coll.FindId(42).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 24)
|
|
|
|
info, err = coll.UpsertId(47, M{"_id": 47, "n": 47})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
if s.versionAtLeast(2, 6) {
|
|
c.Assert(info.UpsertedId, Equals, 47)
|
|
} else {
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
}
|
|
|
|
err = coll.FindId(47).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 47)
|
|
}
|
|
|
|
func (s *S) TestUpdateAll(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"k": n, "n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
info, err := coll.UpdateAll(M{"k": M{"$gt": 42}}, M{"$unset": M{"missing": 1}})
|
|
c.Assert(err, IsNil)
|
|
if s.versionAtLeast(2, 6) {
|
|
c.Assert(info.Updated, Equals, 0)
|
|
c.Assert(info.Matched, Equals, 4)
|
|
} else {
|
|
c.Assert(info.Updated, Equals, 4)
|
|
c.Assert(info.Matched, Equals, 4)
|
|
}
|
|
|
|
info, err = coll.UpdateAll(M{"k": M{"$gt": 42}}, M{"$inc": M{"n": 1}})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 4)
|
|
c.Assert(info.Matched, Equals, 4)
|
|
|
|
result := make(M)
|
|
err = coll.Find(M{"k": 42}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 42)
|
|
|
|
err = coll.Find(M{"k": 43}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 44)
|
|
|
|
err = coll.Find(M{"k": 44}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 45)
|
|
|
|
if !s.versionAtLeast(2, 6) {
|
|
// 2.6 made this invalid.
|
|
info, err = coll.UpdateAll(M{"k": 47}, M{"k": 47, "n": 47})
|
|
c.Assert(err, Equals, nil)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestRemove(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
err = coll.Remove(M{"n": M{"$gt": 42}})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := &struct{ N int }{}
|
|
err = coll.Find(M{"n": 42}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 42)
|
|
|
|
err = coll.Find(M{"n": 43}).One(result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.Find(M{"n": 44}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 44)
|
|
}
|
|
|
|
func (s *S) TestRemoveId(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"_id": 40}, M{"_id": 41}, M{"_id": 42})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.RemoveId(41)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(coll.FindId(40).One(nil), IsNil)
|
|
c.Assert(coll.FindId(41).One(nil), Equals, mgo.ErrNotFound)
|
|
c.Assert(coll.FindId(42).One(nil), IsNil)
|
|
}
|
|
|
|
func (s *S) TestRemoveUnsafe(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
session.SetSafe(nil)
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"_id": 40}, M{"_id": 41}, M{"_id": 42})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.RemoveId(41)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(coll.FindId(40).One(nil), IsNil)
|
|
c.Assert(coll.FindId(41).One(nil), Equals, mgo.ErrNotFound)
|
|
c.Assert(coll.FindId(42).One(nil), IsNil)
|
|
}
|
|
|
|
func (s *S) TestRemoveAll(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
info, err := coll.RemoveAll(M{"n": M{"$gt": 42}})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
c.Assert(info.Removed, Equals, 4)
|
|
c.Assert(info.Matched, Equals, 4)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
result := &struct{ N int }{}
|
|
err = coll.Find(M{"n": 42}).One(result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 42)
|
|
|
|
err = coll.Find(M{"n": 43}).One(result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.Find(M{"n": 44}).One(result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
info, err = coll.RemoveAll(nil)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
c.Assert(info.Removed, Equals, 3)
|
|
c.Assert(info.Matched, Equals, 3)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
n, err := coll.Find(nil).Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 0)
|
|
}
|
|
|
|
func (s *S) TestDropDatabase(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
db1 := session.DB("db1")
|
|
db1.C("col").Insert(M{"_id": 1})
|
|
|
|
db2 := session.DB("db2")
|
|
db2.C("col").Insert(M{"_id": 1})
|
|
|
|
err = db1.DropDatabase()
|
|
c.Assert(err, IsNil)
|
|
|
|
names, err := session.DatabaseNames()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(filterDBs(names), DeepEquals, []string{"db2"})
|
|
|
|
err = db2.DropDatabase()
|
|
c.Assert(err, IsNil)
|
|
|
|
names, err = session.DatabaseNames()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(filterDBs(names), DeepEquals, []string{})
|
|
}
|
|
|
|
func filterDBs(dbs []string) []string {
|
|
var i int
|
|
for _, name := range dbs {
|
|
switch name {
|
|
case "admin", "local":
|
|
default:
|
|
dbs[i] = name
|
|
i++
|
|
}
|
|
}
|
|
if len(dbs) == 0 {
|
|
return []string{}
|
|
}
|
|
return dbs[:i]
|
|
}
|
|
|
|
func (s *S) TestDropCollection(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
db := session.DB("db1")
|
|
db.C("col1").Insert(M{"_id": 1})
|
|
db.C("col2").Insert(M{"_id": 1})
|
|
|
|
err = db.C("col1").DropCollection()
|
|
c.Assert(err, IsNil)
|
|
|
|
names, err := db.CollectionNames()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(names, DeepEquals, []string{"col2", "system.indexes"})
|
|
|
|
err = db.C("col2").DropCollection()
|
|
c.Assert(err, IsNil)
|
|
|
|
names, err = db.CollectionNames()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(names, DeepEquals, []string{"system.indexes"})
|
|
}
|
|
|
|
func (s *S) TestCreateCollectionCapped(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
info := &mgo.CollectionInfo{
|
|
Capped: true,
|
|
MaxBytes: 1024,
|
|
MaxDocs: 3,
|
|
}
|
|
err = coll.Create(info)
|
|
c.Assert(err, IsNil)
|
|
|
|
ns := []int{1, 2, 3, 4, 5}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
n, err := coll.Find(nil).Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 3)
|
|
}
|
|
|
|
func (s *S) TestCreateCollectionNoIndex(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
info := &mgo.CollectionInfo{
|
|
DisableIdIndex: true,
|
|
}
|
|
err = coll.Create(info)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{"n": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
indexes, err := coll.Indexes()
|
|
c.Assert(indexes, HasLen, 0)
|
|
}
|
|
|
|
func (s *S) TestCreateCollectionForceIndex(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
info := &mgo.CollectionInfo{
|
|
ForceIdIndex: true,
|
|
Capped: true,
|
|
MaxBytes: 1024,
|
|
}
|
|
err = coll.Create(info)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{"n": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
indexes, err := coll.Indexes()
|
|
c.Assert(indexes, HasLen, 1)
|
|
}
|
|
|
|
func (s *S) TestCreateCollectionValidator(c *C) {
|
|
if !s.versionAtLeast(3, 2) {
|
|
c.Skip("validation depends on MongoDB 3.2+")
|
|
}
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
db := session.DB("mydb")
|
|
coll := db.C("mycoll")
|
|
|
|
// Test Validator.
|
|
info := &mgo.CollectionInfo{
|
|
Validator: M{"b": M{"$exists": true}},
|
|
}
|
|
err = coll.Create(info)
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"a": 1})
|
|
c.Assert(err, ErrorMatches, "Document failed validation")
|
|
err = coll.DropCollection()
|
|
c.Assert(err, IsNil)
|
|
|
|
// Test ValidatorAction.
|
|
info = &mgo.CollectionInfo{
|
|
Validator: M{"b": M{"$exists": true}},
|
|
ValidationAction: "warn",
|
|
}
|
|
err = coll.Create(info)
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"a": 1})
|
|
c.Assert(err, IsNil)
|
|
err = coll.DropCollection()
|
|
c.Assert(err, IsNil)
|
|
|
|
// Test ValidationLevel.
|
|
info = &mgo.CollectionInfo{
|
|
Validator: M{"a": M{"$exists": true}},
|
|
ValidationLevel: "moderate",
|
|
}
|
|
err = coll.Create(info)
|
|
err = coll.Insert(M{"a": 1})
|
|
c.Assert(err, IsNil)
|
|
err = db.Run(bson.D{{"collMod", "mycoll"}, {"validator", M{"b": M{"$exists": true}}}}, nil)
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"a": 2})
|
|
c.Assert(err, ErrorMatches, "Document failed validation")
|
|
err = coll.Update(M{"a": 1}, M{"c": 1})
|
|
c.Assert(err, IsNil)
|
|
err = coll.DropCollection()
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *S) TestCreateCollectionStorageEngine(c *C) {
|
|
if !s.versionAtLeast(3, 0) {
|
|
c.Skip("storageEngine option depends on MongoDB 3.0+")
|
|
}
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
db := session.DB("mydb")
|
|
coll := db.C("mycoll")
|
|
|
|
info := &mgo.CollectionInfo{
|
|
StorageEngine: M{"test": M{}},
|
|
}
|
|
err = coll.Create(info)
|
|
c.Assert(err, ErrorMatches, "test is not a registered storage engine for this server")
|
|
}
|
|
|
|
func (s *S) TestIsDupValues(c *C) {
|
|
c.Assert(mgo.IsDup(nil), Equals, false)
|
|
c.Assert(mgo.IsDup(&mgo.LastError{Code: 1}), Equals, false)
|
|
c.Assert(mgo.IsDup(&mgo.QueryError{Code: 1}), Equals, false)
|
|
c.Assert(mgo.IsDup(&mgo.LastError{Code: 11000}), Equals, true)
|
|
c.Assert(mgo.IsDup(&mgo.QueryError{Code: 11000}), Equals, true)
|
|
c.Assert(mgo.IsDup(&mgo.LastError{Code: 11001}), Equals, true)
|
|
c.Assert(mgo.IsDup(&mgo.QueryError{Code: 11001}), Equals, true)
|
|
c.Assert(mgo.IsDup(&mgo.LastError{Code: 12582}), Equals, true)
|
|
c.Assert(mgo.IsDup(&mgo.QueryError{Code: 12582}), Equals, true)
|
|
lerr := &mgo.LastError{Code: 16460, Err: "error inserting 1 documents to shard ... caused by :: E11000 duplicate key error index: ..."}
|
|
c.Assert(mgo.IsDup(lerr), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestIsDupPrimary(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"_id": 1})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"_id": 1})
|
|
c.Assert(err, ErrorMatches, ".*duplicate key error.*")
|
|
c.Assert(mgo.IsDup(err), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestIsDupUnique(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
index := mgo.Index{
|
|
Key: []string{"a", "b"},
|
|
Unique: true,
|
|
}
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndex(index)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{"a": 1, "b": 1})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"a": 1, "b": 1})
|
|
c.Assert(err, ErrorMatches, ".*duplicate key error.*")
|
|
c.Assert(mgo.IsDup(err), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestIsDupCapped(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
info := &mgo.CollectionInfo{
|
|
ForceIdIndex: true,
|
|
Capped: true,
|
|
MaxBytes: 1024,
|
|
}
|
|
err = coll.Create(info)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{"_id": 1})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"_id": 1})
|
|
// The error was different for capped collections before 2.6.
|
|
c.Assert(err, ErrorMatches, ".*duplicate key.*")
|
|
// The issue is reduced by using IsDup.
|
|
c.Assert(mgo.IsDup(err), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestIsDupFindAndModify(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndex(mgo.Index{Key: []string{"n"}, Unique: true})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{"n": 1})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"n": 2})
|
|
c.Assert(err, IsNil)
|
|
_, err = coll.Find(M{"n": 1}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}}, bson.M{})
|
|
c.Assert(err, ErrorMatches, ".*duplicate key error.*")
|
|
c.Assert(mgo.IsDup(err), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestIsDupRetryUpsert(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(bson.M{"_id": 1, "x": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = coll.Upsert(bson.M{"_id": 1, "x": 2}, bson.M{"$set": bson.M{"x": 3}})
|
|
c.Assert(mgo.IsDup(err), Equals, true)
|
|
|
|
_, err = coll.Find(bson.M{"_id": 1, "x": 2}).Apply(mgo.Change{
|
|
Update: bson.M{"$set": bson.M{"x": 3}},
|
|
Upsert: true,
|
|
}, nil)
|
|
c.Assert(mgo.IsDup(err), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestFindAndModify(c *C) {
|
|
session, err := mgo.Dial("localhost:40011")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"n": 42})
|
|
|
|
session.SetMode(mgo.Monotonic, true)
|
|
|
|
result := M{}
|
|
info, err := coll.Find(M{"n": 42}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}}, result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 42)
|
|
c.Assert(info.Updated, Equals, 1)
|
|
c.Assert(info.Matched, Equals, 1)
|
|
c.Assert(info.Removed, Equals, 0)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
// A nil result parameter should be acceptable.
|
|
info, err = coll.Find(M{"n": 43}).Apply(mgo.Change{Update: M{"$unset": M{"missing": 1}}}, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Updated, Equals, 1) // On 2.6+ this feels like a server mistake.
|
|
c.Assert(info.Matched, Equals, 1)
|
|
c.Assert(info.Removed, Equals, 0)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
result = M{}
|
|
info, err = coll.Find(M{"n": 43}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}, ReturnNew: true}, result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 44)
|
|
c.Assert(info.Updated, Equals, 1)
|
|
c.Assert(info.Removed, Equals, 0)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
result = M{}
|
|
info, err = coll.Find(M{"n": 50}).Apply(mgo.Change{Upsert: true, Update: M{"n": 51, "o": 52}}, result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], IsNil)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
c.Assert(info.Removed, Equals, 0)
|
|
c.Assert(info.UpsertedId, NotNil)
|
|
|
|
result = M{}
|
|
info, err = coll.Find(nil).Sort("-n").Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}, ReturnNew: true}, result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], Equals, 52)
|
|
c.Assert(info.Updated, Equals, 1)
|
|
c.Assert(info.Removed, Equals, 0)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
result = M{}
|
|
info, err = coll.Find(M{"n": 52}).Select(M{"o": 1}).Apply(mgo.Change{Remove: true}, result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result["n"], IsNil)
|
|
c.Assert(result["o"], Equals, 52)
|
|
c.Assert(info.Updated, Equals, 0)
|
|
c.Assert(info.Removed, Equals, 1)
|
|
c.Assert(info.UpsertedId, IsNil)
|
|
|
|
result = M{}
|
|
info, err = coll.Find(M{"n": 60}).Apply(mgo.Change{Remove: true}, result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
c.Assert(len(result), Equals, 0)
|
|
c.Assert(info, IsNil)
|
|
}
|
|
|
|
func (s *S) TestFindAndModifyBug997828(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"n": "not-a-number"})
|
|
|
|
result := make(M)
|
|
_, err = coll.Find(M{"n": "not-a-number"}).Apply(mgo.Change{Update: M{"$inc": M{"n": 1}}}, result)
|
|
c.Assert(err, ErrorMatches, `(exception: )?Cannot apply \$inc .*`)
|
|
if s.versionAtLeast(2, 1) {
|
|
qerr, _ := err.(*mgo.QueryError)
|
|
c.Assert(qerr, NotNil, Commentf("err: %#v", err))
|
|
if s.versionAtLeast(2, 6) {
|
|
// Oh, the dance of error codes. :-(
|
|
c.Assert(qerr.Code, Equals, 16837)
|
|
} else {
|
|
c.Assert(qerr.Code, Equals, 10140)
|
|
}
|
|
} else {
|
|
lerr, _ := err.(*mgo.LastError)
|
|
c.Assert(lerr, NotNil, Commentf("err: %#v", err))
|
|
c.Assert(lerr.Code, Equals, 10140)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestFindAndModifyErrmsgDoc(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"errmsg": "an error"})
|
|
|
|
var result M
|
|
_, err = coll.Find(M{}).Apply(mgo.Change{Update: M{"$set": M{"n": 1}}}, &result)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *S) TestCountCollection(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
n, err := coll.Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 3)
|
|
}
|
|
|
|
func (s *S) TestCountQuery(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
n, err := coll.Find(M{"n": M{"$gt": 40}}).Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 2)
|
|
}
|
|
|
|
func (s *S) TestCountQuerySorted(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
n, err := coll.Find(M{"n": M{"$gt": 40}}).Sort("n").Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 2)
|
|
}
|
|
|
|
func (s *S) TestCountSkipLimit(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
n, err := coll.Find(nil).Skip(1).Limit(3).Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 3)
|
|
|
|
n, err = coll.Find(nil).Skip(1).Limit(5).Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 4)
|
|
}
|
|
|
|
func (s *S) TestQueryExplain(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
m := M{}
|
|
query := coll.Find(nil).Limit(2)
|
|
err = query.Explain(m)
|
|
c.Assert(err, IsNil)
|
|
if m["queryPlanner"] != nil {
|
|
c.Assert(m["executionStats"].(M)["totalDocsExamined"], Equals, 2)
|
|
} else {
|
|
c.Assert(m["cursor"], Equals, "BasicCursor")
|
|
c.Assert(m["nscanned"], Equals, 2)
|
|
c.Assert(m["n"], Equals, 2)
|
|
}
|
|
|
|
n := 0
|
|
var result M
|
|
iter := query.Iter()
|
|
for iter.Next(&result) {
|
|
n++
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
c.Assert(n, Equals, 2)
|
|
}
|
|
|
|
func (s *S) TestQuerySetMaxScan(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
query := coll.Find(nil).SetMaxScan(2)
|
|
var result []M
|
|
err = query.All(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result, HasLen, 2)
|
|
}
|
|
|
|
func (s *S) TestQuerySetMaxTime(c *C) {
|
|
if !s.versionAtLeast(2, 6) {
|
|
c.Skip("SetMaxTime only supported in 2.6+")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
err := coll.Insert(M{"n": i})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
query := coll.Find(nil)
|
|
query.SetMaxTime(1 * time.Millisecond)
|
|
query.Batch(2)
|
|
var result []M
|
|
err = query.All(&result)
|
|
c.Assert(err, ErrorMatches, "operation exceeded time limit")
|
|
}
|
|
|
|
func (s *S) TestQueryHint(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
coll.EnsureIndexKey("a")
|
|
|
|
m := M{}
|
|
err = coll.Find(nil).Hint("a").Explain(m)
|
|
c.Assert(err, IsNil)
|
|
|
|
if m["queryPlanner"] != nil {
|
|
m = m["queryPlanner"].(M)
|
|
m = m["winningPlan"].(M)
|
|
m = m["inputStage"].(M)
|
|
c.Assert(m["indexName"], Equals, "a_1")
|
|
} else {
|
|
c.Assert(m["indexBounds"], NotNil)
|
|
c.Assert(m["indexBounds"].(M)["a"], NotNil)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestQueryComment(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
db := session.DB("mydb")
|
|
coll := db.C("mycoll")
|
|
|
|
err = db.Run(bson.M{"profile": 2}, nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
ns := []int{40, 41, 42}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
query := coll.Find(bson.M{"n": 41})
|
|
query.Comment("some comment")
|
|
err = query.One(nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
query = coll.Find(bson.M{"n": 41})
|
|
query.Comment("another comment")
|
|
err = query.One(nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
commentField := "query.$comment"
|
|
nField := "query.$query.n"
|
|
if s.versionAtLeast(3, 2) {
|
|
commentField = "query.comment"
|
|
nField = "query.filter.n"
|
|
}
|
|
n, err := session.DB("mydb").C("system.profile").Find(bson.M{nField: 41, commentField: "some comment"}).Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestFindOneNotFound(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
result := struct{ A, B int }{}
|
|
err = coll.Find(M{"a": 1}).One(&result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
c.Assert(err, ErrorMatches, "not found")
|
|
c.Assert(err == mgo.ErrNotFound, Equals, true)
|
|
}
|
|
|
|
func (s *S) TestFindIterNotFound(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
result := struct{ A, B int }{}
|
|
iter := coll.Find(M{"a": 1}).Iter()
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Err(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestFindNil(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Insert(M{"n": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := struct{ N int }{}
|
|
|
|
err = coll.Find(nil).One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestFindId(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Insert(M{"_id": 41, "n": 41})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"_id": 42, "n": 42})
|
|
c.Assert(err, IsNil)
|
|
|
|
result := struct{ N int }{}
|
|
|
|
err = coll.FindId(42).One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.N, Equals, 42)
|
|
}
|
|
|
|
func (s *S) TestFindIterAll(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
iter := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2).Iter()
|
|
result := struct{ N int }{}
|
|
for i := 2; i < 7; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true, Commentf("err=%v", err))
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 1 {
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Close(), IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 3) // and their REPLY_OPs.
|
|
if s.versionAtLeast(3, 2) {
|
|
// In 3.2+ responses come in batches inside the op reply docs.
|
|
c.Assert(stats.ReceivedDocs, Equals, 3)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 5)
|
|
}
|
|
c.Assert(stats.SocketsInUse, Equals, 0)
|
|
}
|
|
|
|
func (s *S) TestFindIterTwiceWithSameQuery(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for i := 40; i != 47; i++ {
|
|
err := coll.Insert(M{"n": i})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
query := coll.Find(M{}).Sort("n")
|
|
|
|
iter1 := query.Skip(1).Iter()
|
|
iter2 := query.Skip(2).Iter()
|
|
|
|
var result struct{ N int }
|
|
ok := iter2.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.N, Equals, 42)
|
|
ok = iter1.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.N, Equals, 41)
|
|
}
|
|
|
|
func (s *S) TestFindIterWithoutResults(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
coll.Insert(M{"n": 42})
|
|
|
|
iter := coll.Find(M{"n": 0}).Iter()
|
|
|
|
result := struct{ N int }{}
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Close(), IsNil)
|
|
c.Assert(result.N, Equals, 0)
|
|
}
|
|
|
|
func (s *S) TestFindIterLimit(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Limit(3)
|
|
iter := query.Iter()
|
|
|
|
result := struct{ N int }{}
|
|
for i := 2; i < 5; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
}
|
|
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Close(), IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
stats := mgo.GetStats()
|
|
if s.versionAtLeast(3, 2) {
|
|
// Limit works properly in 3.2+, and results are batched in single doc.
|
|
c.Assert(stats.SentOps, Equals, 1) // 1*QUERY_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 1) // and its REPLY_OP
|
|
c.Assert(stats.ReceivedDocs, Equals, 1)
|
|
} else {
|
|
c.Assert(stats.SentOps, Equals, 2) // 1*QUERY_OP + 1*KILL_CURSORS_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 1) // and its REPLY_OP
|
|
c.Assert(stats.ReceivedDocs, Equals, 3)
|
|
}
|
|
c.Assert(stats.SocketsInUse, Equals, 0)
|
|
}
|
|
|
|
var cursorTimeout = flag.Bool("cursor-timeout", false, "Enable cursor timeout test")
|
|
|
|
func (s *S) TestFindIterCursorTimeout(c *C) {
|
|
if !*cursorTimeout {
|
|
c.Skip("-cursor-timeout")
|
|
}
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
type Doc struct {
|
|
Id int "_id"
|
|
}
|
|
|
|
coll := session.DB("test").C("test")
|
|
coll.Remove(nil)
|
|
for i := 0; i < 100; i++ {
|
|
err = coll.Insert(Doc{i})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
session.SetBatch(1)
|
|
iter := coll.Find(nil).Iter()
|
|
var doc Doc
|
|
if !iter.Next(&doc) {
|
|
c.Fatalf("iterator failed to return any documents")
|
|
}
|
|
|
|
for i := 10; i > 0; i-- {
|
|
c.Logf("Sleeping... %d minutes to go...", i)
|
|
time.Sleep(1*time.Minute + 2*time.Second)
|
|
}
|
|
|
|
// Drain any existing documents that were fetched.
|
|
if !iter.Next(&doc) {
|
|
c.Fatalf("iterator with timed out cursor failed to return previously cached document")
|
|
}
|
|
if iter.Next(&doc) {
|
|
c.Fatalf("timed out cursor returned document")
|
|
}
|
|
|
|
c.Assert(iter.Err(), Equals, mgo.ErrCursor)
|
|
}
|
|
|
|
func (s *S) TestTooManyItemsLimitBug(c *C) {
|
|
if *fast {
|
|
c.Skip("-fast")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
|
|
|
|
mgo.SetDebug(false)
|
|
coll := session.DB("mydb").C("mycoll")
|
|
words := strings.Split("foo bar baz", " ")
|
|
for i := 0; i < 5; i++ {
|
|
words = append(words, words...)
|
|
}
|
|
doc := bson.D{{"words", words}}
|
|
inserts := 10000
|
|
limit := 5000
|
|
iters := 0
|
|
c.Assert(inserts > limit, Equals, true)
|
|
for i := 0; i < inserts; i++ {
|
|
err := coll.Insert(&doc)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
iter := coll.Find(nil).Limit(limit).Iter()
|
|
for iter.Next(&doc) {
|
|
if iters%100 == 0 {
|
|
c.Logf("Seen %d docments", iters)
|
|
}
|
|
iters++
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
c.Assert(iters, Equals, limit)
|
|
}
|
|
|
|
func (s *S) TestBatchSizeZeroGetMore(c *C) {
|
|
if *fast {
|
|
c.Skip("-fast")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
|
|
|
|
mgo.SetDebug(false)
|
|
coll := session.DB("mydb").C("mycoll")
|
|
words := strings.Split("foo bar baz", " ")
|
|
for i := 0; i < 5; i++ {
|
|
words = append(words, words...)
|
|
}
|
|
doc := bson.D{{"words", words}}
|
|
inserts := 10000
|
|
iters := 0
|
|
for i := 0; i < inserts; i++ {
|
|
err := coll.Insert(&doc)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
iter := coll.Find(nil).Iter()
|
|
for iter.Next(&doc) {
|
|
if iters%100 == 0 {
|
|
c.Logf("Seen %d docments", iters)
|
|
}
|
|
iters++
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func serverCursorsOpen(session *mgo.Session) int {
|
|
var result struct {
|
|
Cursors struct {
|
|
TotalOpen int `bson:"totalOpen"`
|
|
TimedOut int `bson:"timedOut"`
|
|
}
|
|
}
|
|
err := session.Run("serverStatus", &result)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return result.Cursors.TotalOpen
|
|
}
|
|
|
|
func (s *S) TestFindIterLimitWithMore(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
// Insane amounts of logging otherwise due to the
|
|
// amount of data being shuffled.
|
|
mgo.SetDebug(false)
|
|
defer mgo.SetDebug(true)
|
|
|
|
// Should amount to more than 4MB bson payload,
|
|
// the default limit per result chunk.
|
|
const total = 4096
|
|
var d struct{ A [1024]byte }
|
|
docs := make([]interface{}, total)
|
|
for i := 0; i < total; i++ {
|
|
docs[i] = &d
|
|
}
|
|
err = coll.Insert(docs...)
|
|
c.Assert(err, IsNil)
|
|
|
|
n, err := coll.Count()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(n, Equals, total)
|
|
|
|
// First, try restricting to a single chunk with a negative limit.
|
|
nresults := 0
|
|
iter := coll.Find(nil).Limit(-total).Iter()
|
|
var discard struct{}
|
|
for iter.Next(&discard) {
|
|
nresults++
|
|
}
|
|
if nresults < total/2 || nresults >= total {
|
|
c.Fatalf("Bad result size with negative limit: %d", nresults)
|
|
}
|
|
|
|
cursorsOpen := serverCursorsOpen(session)
|
|
|
|
// Try again, with a positive limit. Should reach the end now,
|
|
// using multiple chunks.
|
|
nresults = 0
|
|
iter = coll.Find(nil).Limit(total).Iter()
|
|
for iter.Next(&discard) {
|
|
nresults++
|
|
}
|
|
c.Assert(nresults, Equals, total)
|
|
|
|
// Ensure the cursor used is properly killed.
|
|
c.Assert(serverCursorsOpen(session), Equals, cursorsOpen)
|
|
|
|
// Edge case, -MinInt == -MinInt.
|
|
nresults = 0
|
|
iter = coll.Find(nil).Limit(math.MinInt32).Iter()
|
|
for iter.Next(&discard) {
|
|
nresults++
|
|
}
|
|
if nresults < total/2 || nresults >= total {
|
|
c.Fatalf("Bad result size with MinInt32 limit: %d", nresults)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestFindIterLimitWithBatch(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
// Ping the database to ensure the nonce has been received already.
|
|
c.Assert(session.Ping(), IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Limit(3).Batch(2)
|
|
iter := query.Iter()
|
|
result := struct{ N int }{}
|
|
for i := 2; i < 5; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 3 {
|
|
stats := mgo.GetStats()
|
|
if s.versionAtLeast(3, 2) {
|
|
// In 3.2+ responses come in batches inside the op reply docs.
|
|
c.Assert(stats.ReceivedDocs, Equals, 1)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
}
|
|
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Close(), IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
stats := mgo.GetStats()
|
|
if s.versionAtLeast(3, 2) {
|
|
// In 3.2+ limit works properly even with multiple batches..
|
|
c.Assert(stats.SentOps, Equals, 2) // 1*QUERY_OP + 1*GET_MORE_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 2) // and its REPLY_OPs
|
|
|
|
// In 3.2+ responses come in batches inside the op reply docs.
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
} else {
|
|
c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 1*GET_MORE_OP + 1*KILL_CURSORS_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 2) // and its REPLY_OPs
|
|
c.Assert(stats.ReceivedDocs, Equals, 3)
|
|
}
|
|
c.Assert(stats.SocketsInUse, Equals, 0)
|
|
}
|
|
|
|
func (s *S) TestFindIterSortWithBatch(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
// Without this, the logic above breaks because Mongo refuses to
|
|
// return a cursor with an in-memory sort.
|
|
coll.EnsureIndexKey("n")
|
|
|
|
// Ping the database to ensure the nonce has been received already.
|
|
c.Assert(session.Ping(), IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
query := coll.Find(M{"n": M{"$lte": 44}}).Sort("-n").Batch(2)
|
|
iter := query.Iter()
|
|
ns = []int{46, 45, 44, 43, 42, 41, 40}
|
|
result := struct{ N int }{}
|
|
for i := 2; i < len(ns); i++ {
|
|
c.Logf("i=%d", i)
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 3 {
|
|
stats := mgo.GetStats()
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, 1)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
}
|
|
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Close(), IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 3) // and its REPLY_OPs
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, 3)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 5)
|
|
}
|
|
c.Assert(stats.SocketsInUse, Equals, 0)
|
|
}
|
|
|
|
// Test tailable cursors in a situation where Next has to sleep to
|
|
// respect the timeout requested on Tail.
|
|
func (s *S) TestFindTailTimeoutWithSleep(c *C) {
|
|
if *fast {
|
|
c.Skip("-fast")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
cresult := struct{ ErrMsg string }{}
|
|
|
|
db := session.DB("mydb")
|
|
err = db.Run(bson.D{{"create", "mycoll"}, {"capped", true}, {"size", 1024}}, &cresult)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cresult.ErrMsg, Equals, "")
|
|
coll := db.C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
timeout := 5 * time.Second
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
|
|
iter := query.Tail(timeout)
|
|
|
|
n := len(ns)
|
|
result := struct{ N int }{}
|
|
for i := 2; i != n; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 3 { // The batch boundary.
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
|
|
mgo.ResetStats()
|
|
|
|
// The following call to Next will block.
|
|
done := make(chan bool)
|
|
defer func() { <-done }()
|
|
go func() {
|
|
// The internal AwaitData timing of MongoDB is around 2 seconds,
|
|
// so this should force mgo to sleep at least once by itself to
|
|
// respect the requested timeout.
|
|
c.Logf("[GOROUTINE] Starting and sleeping...")
|
|
time.Sleep(timeout - 2*time.Second)
|
|
c.Logf("[GOROUTINE] Woke up...")
|
|
session := session.New()
|
|
c.Logf("[GOROUTINE] Session created and will insert...")
|
|
err := coll.Insert(M{"n": 47})
|
|
c.Logf("[GOROUTINE] Insert attempted, err=%v...", err)
|
|
session.Close()
|
|
c.Logf("[GOROUTINE] Session closed.")
|
|
c.Check(err, IsNil)
|
|
done <- true
|
|
}()
|
|
|
|
c.Log("Will wait for Next with N=47...")
|
|
ok := iter.Next(&result)
|
|
c.Log("Next unblocked...")
|
|
c.Assert(ok, Equals, true)
|
|
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
c.Assert(result.N, Equals, 47)
|
|
c.Log("Got Next with N=47!")
|
|
|
|
c.Log("Will wait for a result which will never come...")
|
|
|
|
started := time.Now()
|
|
ok = iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, true)
|
|
c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
|
|
|
|
c.Log("Will now reuse the timed out tail cursor...")
|
|
|
|
coll.Insert(M{"n": 48})
|
|
ok = iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(iter.Close(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
c.Assert(result.N, Equals, 48)
|
|
}
|
|
|
|
// Test tailable cursors in a situation where Next never gets to sleep once
|
|
// to respect the timeout requested on Tail.
|
|
func (s *S) TestFindTailTimeoutNoSleep(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
cresult := struct{ ErrMsg string }{}
|
|
|
|
db := session.DB("mydb")
|
|
err = db.Run(bson.D{{"create", "mycoll"}, {"capped", true}, {"size", 1024}}, &cresult)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cresult.ErrMsg, Equals, "")
|
|
coll := db.C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
timeout := 1 * time.Second
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
|
|
iter := query.Tail(timeout)
|
|
|
|
n := len(ns)
|
|
result := struct{ N int }{}
|
|
for i := 2; i != n; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 3 { // The batch boundary.
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
|
|
// The following call to Next will block.
|
|
go func() {
|
|
// The internal AwaitData timing of MongoDB is around 2 seconds,
|
|
// so this item should arrive within the AwaitData threshold.
|
|
time.Sleep(500 * time.Millisecond)
|
|
session := session.New()
|
|
defer session.Close()
|
|
coll := session.DB("mydb").C("mycoll")
|
|
coll.Insert(M{"n": 47})
|
|
}()
|
|
|
|
c.Log("Will wait for Next with N=47...")
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
c.Assert(result.N, Equals, 47)
|
|
c.Log("Got Next with N=47!")
|
|
|
|
c.Log("Will wait for a result which will never come...")
|
|
|
|
started := time.Now()
|
|
ok = iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, true)
|
|
c.Assert(started.Before(time.Now().Add(-timeout)), Equals, true)
|
|
|
|
c.Log("Will now reuse the timed out tail cursor...")
|
|
|
|
coll.Insert(M{"n": 48})
|
|
ok = iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(iter.Close(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
c.Assert(result.N, Equals, 48)
|
|
}
|
|
|
|
// Test tailable cursors in a situation where Next never gets to sleep once
|
|
// to respect the timeout requested on Tail.
|
|
func (s *S) TestFindTailNoTimeout(c *C) {
|
|
if *fast {
|
|
c.Skip("-fast")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
cresult := struct{ ErrMsg string }{}
|
|
|
|
db := session.DB("mydb")
|
|
err = db.Run(bson.D{{"create", "mycoll"}, {"capped", true}, {"size", 1024}}, &cresult)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cresult.ErrMsg, Equals, "")
|
|
coll := db.C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
|
|
iter := query.Tail(-1)
|
|
c.Assert(err, IsNil)
|
|
|
|
n := len(ns)
|
|
result := struct{ N int }{}
|
|
for i := 2; i != n; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 3 { // The batch boundary.
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
|
|
mgo.ResetStats()
|
|
|
|
// The following call to Next will block.
|
|
go func() {
|
|
time.Sleep(5e8)
|
|
session := session.New()
|
|
defer session.Close()
|
|
coll := session.DB("mydb").C("mycoll")
|
|
coll.Insert(M{"n": 47})
|
|
}()
|
|
|
|
c.Log("Will wait for Next with N=47...")
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
c.Assert(result.N, Equals, 47)
|
|
c.Log("Got Next with N=47!")
|
|
|
|
c.Log("Will wait for a result which will never come...")
|
|
|
|
gotNext := make(chan bool)
|
|
go func() {
|
|
ok := iter.Next(&result)
|
|
gotNext <- ok
|
|
}()
|
|
|
|
select {
|
|
case ok := <-gotNext:
|
|
c.Fatalf("Next returned: %v", ok)
|
|
case <-time.After(3e9):
|
|
// Good. Should still be sleeping at that point.
|
|
}
|
|
|
|
// Closing the session should cause Next to return.
|
|
session.Close()
|
|
|
|
select {
|
|
case ok := <-gotNext:
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Err(), ErrorMatches, "Closed explicitly")
|
|
c.Assert(iter.Timeout(), Equals, false)
|
|
case <-time.After(1e9):
|
|
c.Fatal("Closing the session did not unblock Next")
|
|
}
|
|
}
|
|
|
|
func (s *S) TestIterNextResetsResult(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{1, 2, 3}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n" + strconv.Itoa(n): n})
|
|
}
|
|
|
|
query := coll.Find(nil).Sort("$natural")
|
|
|
|
i := 0
|
|
var sresult *struct{ N1, N2, N3 int }
|
|
iter := query.Iter()
|
|
for iter.Next(&sresult) {
|
|
switch i {
|
|
case 0:
|
|
c.Assert(sresult.N1, Equals, 1)
|
|
c.Assert(sresult.N2+sresult.N3, Equals, 0)
|
|
case 1:
|
|
c.Assert(sresult.N2, Equals, 2)
|
|
c.Assert(sresult.N1+sresult.N3, Equals, 0)
|
|
case 2:
|
|
c.Assert(sresult.N3, Equals, 3)
|
|
c.Assert(sresult.N1+sresult.N2, Equals, 0)
|
|
}
|
|
i++
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
|
|
i = 0
|
|
var mresult M
|
|
iter = query.Iter()
|
|
for iter.Next(&mresult) {
|
|
delete(mresult, "_id")
|
|
switch i {
|
|
case 0:
|
|
c.Assert(mresult, DeepEquals, M{"n1": 1})
|
|
case 1:
|
|
c.Assert(mresult, DeepEquals, M{"n2": 2})
|
|
case 2:
|
|
c.Assert(mresult, DeepEquals, M{"n3": 3})
|
|
}
|
|
i++
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
|
|
i = 0
|
|
var iresult interface{}
|
|
iter = query.Iter()
|
|
for iter.Next(&iresult) {
|
|
mresult, ok := iresult.(bson.M)
|
|
c.Assert(ok, Equals, true, Commentf("%#v", iresult))
|
|
delete(mresult, "_id")
|
|
switch i {
|
|
case 0:
|
|
c.Assert(mresult, DeepEquals, bson.M{"n1": 1})
|
|
case 1:
|
|
c.Assert(mresult, DeepEquals, bson.M{"n2": 2})
|
|
case 2:
|
|
c.Assert(mresult, DeepEquals, bson.M{"n3": 3})
|
|
}
|
|
i++
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestFindForOnIter(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
|
|
iter := query.Iter()
|
|
|
|
i := 2
|
|
var result *struct{ N int }
|
|
err = iter.For(&result, func() error {
|
|
c.Assert(i < 7, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 1 {
|
|
stats := mgo.GetStats()
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, 1)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
i++
|
|
return nil
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 3) // and their REPLY_OPs.
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, 3)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 5)
|
|
}
|
|
c.Assert(stats.SocketsInUse, Equals, 0)
|
|
}
|
|
|
|
func (s *S) TestFindFor(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
mgo.ResetStats()
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2)
|
|
|
|
i := 2
|
|
var result *struct{ N int }
|
|
err = query.For(&result, func() error {
|
|
c.Assert(i < 7, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 1 {
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, 1)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 2)
|
|
}
|
|
}
|
|
i++
|
|
return nil
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
session.Refresh() // Release socket.
|
|
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 3) // 1*QUERY_OP + 2*GET_MORE_OP
|
|
c.Assert(stats.ReceivedOps, Equals, 3) // and their REPLY_OPs.
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, 3)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, 5)
|
|
}
|
|
c.Assert(stats.SocketsInUse, Equals, 0)
|
|
}
|
|
|
|
func (s *S) TestFindForStopOnError(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
query := coll.Find(M{"n": M{"$gte": 42}})
|
|
i := 2
|
|
var result *struct{ N int }
|
|
err = query.For(&result, func() error {
|
|
c.Assert(i < 4, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
if i == 3 {
|
|
return fmt.Errorf("stop!")
|
|
}
|
|
i++
|
|
return nil
|
|
})
|
|
c.Assert(err, ErrorMatches, "stop!")
|
|
}
|
|
|
|
func (s *S) TestFindForResetsResult(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{1, 2, 3}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n" + strconv.Itoa(n): n})
|
|
}
|
|
|
|
query := coll.Find(nil).Sort("$natural")
|
|
|
|
i := 0
|
|
var sresult *struct{ N1, N2, N3 int }
|
|
err = query.For(&sresult, func() error {
|
|
switch i {
|
|
case 0:
|
|
c.Assert(sresult.N1, Equals, 1)
|
|
c.Assert(sresult.N2+sresult.N3, Equals, 0)
|
|
case 1:
|
|
c.Assert(sresult.N2, Equals, 2)
|
|
c.Assert(sresult.N1+sresult.N3, Equals, 0)
|
|
case 2:
|
|
c.Assert(sresult.N3, Equals, 3)
|
|
c.Assert(sresult.N1+sresult.N2, Equals, 0)
|
|
}
|
|
i++
|
|
return nil
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
i = 0
|
|
var mresult M
|
|
err = query.For(&mresult, func() error {
|
|
delete(mresult, "_id")
|
|
switch i {
|
|
case 0:
|
|
c.Assert(mresult, DeepEquals, M{"n1": 1})
|
|
case 1:
|
|
c.Assert(mresult, DeepEquals, M{"n2": 2})
|
|
case 2:
|
|
c.Assert(mresult, DeepEquals, M{"n3": 3})
|
|
}
|
|
i++
|
|
return nil
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
i = 0
|
|
var iresult interface{}
|
|
err = query.For(&iresult, func() error {
|
|
mresult, ok := iresult.(bson.M)
|
|
c.Assert(ok, Equals, true, Commentf("%#v", iresult))
|
|
delete(mresult, "_id")
|
|
switch i {
|
|
case 0:
|
|
c.Assert(mresult, DeepEquals, bson.M{"n1": 1})
|
|
case 1:
|
|
c.Assert(mresult, DeepEquals, bson.M{"n2": 2})
|
|
case 2:
|
|
c.Assert(mresult, DeepEquals, bson.M{"n3": 3})
|
|
}
|
|
i++
|
|
return nil
|
|
})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *S) TestFindIterSnapshot(c *C) {
|
|
if s.versionAtLeast(3, 2) {
|
|
c.Skip("Broken in 3.2: https://jira.mongodb.org/browse/SERVER-21403")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
// Insane amounts of logging otherwise due to the
|
|
// amount of data being shuffled.
|
|
mgo.SetDebug(false)
|
|
defer mgo.SetDebug(true)
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
var a [1024000]byte
|
|
|
|
for n := 0; n < 10; n++ {
|
|
err := coll.Insert(M{"_id": n, "n": n, "a1": &a})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
query := coll.Find(M{"n": M{"$gt": -1}}).Batch(2).Prefetch(0)
|
|
query.Snapshot()
|
|
iter := query.Iter()
|
|
|
|
seen := map[int]bool{}
|
|
result := struct {
|
|
Id int "_id"
|
|
}{}
|
|
for iter.Next(&result) {
|
|
if len(seen) == 2 {
|
|
// Grow all entries so that they have to move.
|
|
// Backwards so that the order is inverted.
|
|
for n := 10; n >= 0; n-- {
|
|
_, err := coll.Upsert(M{"_id": n}, M{"$set": M{"a2": &a}})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
}
|
|
if seen[result.Id] {
|
|
c.Fatalf("seen duplicated key: %d", result.Id)
|
|
}
|
|
seen[result.Id] = true
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestSort(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
coll.Insert(M{"a": 1, "b": 1})
|
|
coll.Insert(M{"a": 2, "b": 2})
|
|
coll.Insert(M{"a": 2, "b": 1})
|
|
coll.Insert(M{"a": 0, "b": 1})
|
|
coll.Insert(M{"a": 2, "b": 0})
|
|
coll.Insert(M{"a": 0, "b": 2})
|
|
coll.Insert(M{"a": 1, "b": 2})
|
|
coll.Insert(M{"a": 0, "b": 0})
|
|
coll.Insert(M{"a": 1, "b": 0})
|
|
|
|
query := coll.Find(M{})
|
|
query.Sort("-a") // Should be ignored.
|
|
query.Sort("-b", "a")
|
|
iter := query.Iter()
|
|
|
|
l := make([]int, 18)
|
|
r := struct{ A, B int }{}
|
|
for i := 0; i != len(l); i += 2 {
|
|
ok := iter.Next(&r)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(err, IsNil)
|
|
l[i] = r.A
|
|
l[i+1] = r.B
|
|
}
|
|
|
|
c.Assert(l, DeepEquals, []int{0, 2, 1, 2, 2, 2, 0, 1, 1, 1, 2, 1, 0, 0, 1, 0, 2, 0})
|
|
}
|
|
|
|
func (s *S) TestSortWithBadArgs(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
f1 := func() { coll.Find(nil).Sort("") }
|
|
f2 := func() { coll.Find(nil).Sort("+") }
|
|
f3 := func() { coll.Find(nil).Sort("foo", "-") }
|
|
|
|
for _, f := range []func(){f1, f2, f3} {
|
|
c.Assert(f, PanicMatches, "Sort: empty field name")
|
|
}
|
|
}
|
|
|
|
func (s *S) TestSortScoreText(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
if !s.versionAtLeast(2, 4) {
|
|
c.Skip("Text search depends on 2.4+")
|
|
}
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndex(mgo.Index{
|
|
Key: []string{"$text:a", "$text:b"},
|
|
})
|
|
msg := "text search not enabled"
|
|
if err != nil && strings.Contains(err.Error(), msg) {
|
|
c.Skip(msg)
|
|
}
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{
|
|
"a": "none",
|
|
"b": "twice: foo foo",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{
|
|
"a": "just once: foo",
|
|
"b": "none",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{
|
|
"a": "many: foo foo foo",
|
|
"b": "none",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{
|
|
"a": "none",
|
|
"b": "none",
|
|
"c": "ignore: foo",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
query := coll.Find(M{"$text": M{"$search": "foo"}})
|
|
query.Select(M{"score": M{"$meta": "textScore"}})
|
|
query.Sort("$textScore:score")
|
|
iter := query.Iter()
|
|
|
|
var r struct{ A, B string }
|
|
var results []string
|
|
for iter.Next(&r) {
|
|
results = append(results, r.A, r.B)
|
|
}
|
|
|
|
c.Assert(results, DeepEquals, []string{
|
|
"many: foo foo foo", "none",
|
|
"none", "twice: foo foo",
|
|
"just once: foo", "none",
|
|
})
|
|
}
|
|
|
|
func (s *S) TestPrefetching(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
const total = 600
|
|
const batch = 100
|
|
mgo.SetDebug(false)
|
|
docs := make([]interface{}, total)
|
|
for i := 0; i != total; i++ {
|
|
docs[i] = bson.D{{"n", i}}
|
|
}
|
|
err = coll.Insert(docs...)
|
|
c.Assert(err, IsNil)
|
|
|
|
for testi := 0; testi < 5; testi++ {
|
|
mgo.ResetStats()
|
|
|
|
var iter *mgo.Iter
|
|
var beforeMore int
|
|
|
|
switch testi {
|
|
case 0: // The default session value.
|
|
session.SetBatch(batch)
|
|
iter = coll.Find(M{}).Iter()
|
|
beforeMore = 75
|
|
|
|
case 2: // Changing the session value.
|
|
session.SetBatch(batch)
|
|
session.SetPrefetch(0.27)
|
|
iter = coll.Find(M{}).Iter()
|
|
beforeMore = 73
|
|
|
|
case 1: // Changing via query methods.
|
|
iter = coll.Find(M{}).Prefetch(0.27).Batch(batch).Iter()
|
|
beforeMore = 73
|
|
|
|
case 3: // With prefetch on first document.
|
|
iter = coll.Find(M{}).Prefetch(1.0).Batch(batch).Iter()
|
|
beforeMore = 0
|
|
|
|
case 4: // Without prefetch.
|
|
iter = coll.Find(M{}).Prefetch(0).Batch(batch).Iter()
|
|
beforeMore = 100
|
|
}
|
|
|
|
pings := 0
|
|
for batchi := 0; batchi < len(docs)/batch-1; batchi++ {
|
|
c.Logf("Iterating over %d documents on batch %d", beforeMore, batchi)
|
|
var result struct{ N int }
|
|
for i := 0; i < beforeMore; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true, Commentf("iter.Err: %v", iter.Err()))
|
|
}
|
|
beforeMore = 99
|
|
c.Logf("Done iterating.")
|
|
|
|
session.Run("ping", nil) // Roundtrip to settle down.
|
|
pings++
|
|
|
|
stats := mgo.GetStats()
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, (batchi+1)+pings)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, (batchi+1)*batch+pings)
|
|
}
|
|
|
|
c.Logf("Iterating over one more document on batch %d", batchi)
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true, Commentf("iter.Err: %v", iter.Err()))
|
|
c.Logf("Done iterating.")
|
|
|
|
session.Run("ping", nil) // Roundtrip to settle down.
|
|
pings++
|
|
|
|
stats = mgo.GetStats()
|
|
if s.versionAtLeast(3, 2) {
|
|
// Find command in 3.2+ bundles batches in a single document.
|
|
c.Assert(stats.ReceivedDocs, Equals, (batchi+2)+pings)
|
|
} else {
|
|
c.Assert(stats.ReceivedDocs, Equals, (batchi+2)*batch+pings)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *S) TestSafeSetting(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
// Check the default
|
|
safe := session.Safe()
|
|
c.Assert(safe.W, Equals, 0)
|
|
c.Assert(safe.WMode, Equals, "")
|
|
c.Assert(safe.WTimeout, Equals, 0)
|
|
c.Assert(safe.FSync, Equals, false)
|
|
c.Assert(safe.J, Equals, false)
|
|
|
|
// Tweak it
|
|
session.SetSafe(&mgo.Safe{W: 1, WTimeout: 2, FSync: true})
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 1)
|
|
c.Assert(safe.WMode, Equals, "")
|
|
c.Assert(safe.WTimeout, Equals, 2)
|
|
c.Assert(safe.FSync, Equals, true)
|
|
c.Assert(safe.J, Equals, false)
|
|
|
|
// Reset it again.
|
|
session.SetSafe(&mgo.Safe{})
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 0)
|
|
c.Assert(safe.WMode, Equals, "")
|
|
c.Assert(safe.WTimeout, Equals, 0)
|
|
c.Assert(safe.FSync, Equals, false)
|
|
c.Assert(safe.J, Equals, false)
|
|
|
|
// Ensure safety to something more conservative.
|
|
session.SetSafe(&mgo.Safe{W: 5, WTimeout: 6, J: true})
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 5)
|
|
c.Assert(safe.WMode, Equals, "")
|
|
c.Assert(safe.WTimeout, Equals, 6)
|
|
c.Assert(safe.FSync, Equals, false)
|
|
c.Assert(safe.J, Equals, true)
|
|
|
|
// Ensure safety to something less conservative won't change it.
|
|
session.EnsureSafe(&mgo.Safe{W: 4, WTimeout: 7})
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 5)
|
|
c.Assert(safe.WMode, Equals, "")
|
|
c.Assert(safe.WTimeout, Equals, 6)
|
|
c.Assert(safe.FSync, Equals, false)
|
|
c.Assert(safe.J, Equals, true)
|
|
|
|
// But to something more conservative will.
|
|
session.EnsureSafe(&mgo.Safe{W: 6, WTimeout: 4, FSync: true})
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 6)
|
|
c.Assert(safe.WMode, Equals, "")
|
|
c.Assert(safe.WTimeout, Equals, 4)
|
|
c.Assert(safe.FSync, Equals, true)
|
|
c.Assert(safe.J, Equals, false)
|
|
|
|
// Even more conservative.
|
|
session.EnsureSafe(&mgo.Safe{WMode: "majority", WTimeout: 2})
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 0)
|
|
c.Assert(safe.WMode, Equals, "majority")
|
|
c.Assert(safe.WTimeout, Equals, 2)
|
|
c.Assert(safe.FSync, Equals, true)
|
|
c.Assert(safe.J, Equals, false)
|
|
|
|
// WMode always overrides, whatever it is, but J doesn't.
|
|
session.EnsureSafe(&mgo.Safe{WMode: "something", J: true})
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 0)
|
|
c.Assert(safe.WMode, Equals, "something")
|
|
c.Assert(safe.WTimeout, Equals, 2)
|
|
c.Assert(safe.FSync, Equals, true)
|
|
c.Assert(safe.J, Equals, false)
|
|
|
|
// EnsureSafe with nil does nothing.
|
|
session.EnsureSafe(nil)
|
|
safe = session.Safe()
|
|
c.Assert(safe.W, Equals, 0)
|
|
c.Assert(safe.WMode, Equals, "something")
|
|
c.Assert(safe.WTimeout, Equals, 2)
|
|
c.Assert(safe.FSync, Equals, true)
|
|
c.Assert(safe.J, Equals, false)
|
|
|
|
// Changing the safety of a cloned session doesn't touch the original.
|
|
clone := session.Clone()
|
|
defer clone.Close()
|
|
clone.EnsureSafe(&mgo.Safe{WMode: "foo"})
|
|
safe = session.Safe()
|
|
c.Assert(safe.WMode, Equals, "something")
|
|
}
|
|
|
|
func (s *S) TestSafeInsert(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
// Insert an element with a predefined key.
|
|
err = coll.Insert(M{"_id": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
mgo.ResetStats()
|
|
|
|
// Session should be safe by default, so inserting it again must fail.
|
|
err = coll.Insert(M{"_id": 1})
|
|
c.Assert(err, ErrorMatches, ".*E11000 duplicate.*")
|
|
c.Assert(err.(*mgo.LastError).Code, Equals, 11000)
|
|
|
|
// It must have sent two operations (INSERT_OP + getLastError QUERY_OP)
|
|
stats := mgo.GetStats()
|
|
|
|
if s.versionAtLeast(2, 6) {
|
|
c.Assert(stats.SentOps, Equals, 1)
|
|
} else {
|
|
c.Assert(stats.SentOps, Equals, 2)
|
|
}
|
|
|
|
mgo.ResetStats()
|
|
|
|
// If we disable safety, though, it won't complain.
|
|
session.SetSafe(nil)
|
|
err = coll.Insert(M{"_id": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
// Must have sent a single operation this time (just the INSERT_OP)
|
|
stats = mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestSafeParameters(c *C) {
|
|
session, err := mgo.Dial("localhost:40011")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
// Tweak the safety parameters to something unachievable.
|
|
session.SetSafe(&mgo.Safe{W: 4, WTimeout: 100})
|
|
err = coll.Insert(M{"_id": 1})
|
|
c.Assert(err, ErrorMatches, "timeout|timed out waiting for slaves|Not enough data-bearing nodes|waiting for replication timed out") // :-(
|
|
if !s.versionAtLeast(2, 6) {
|
|
// 2.6 turned it into a query error.
|
|
c.Assert(err.(*mgo.LastError).WTimeout, Equals, true)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestQueryErrorOne(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Find(M{"a": 1}).Select(M{"a": M{"b": 1}}).One(nil)
|
|
c.Assert(err, ErrorMatches, ".*Unsupported projection option:.*")
|
|
c.Assert(err.(*mgo.QueryError).Message, Matches, ".*Unsupported projection option:.*")
|
|
// Oh, the dance of error codes. :-(
|
|
if s.versionAtLeast(3, 2) {
|
|
c.Assert(err.(*mgo.QueryError).Code, Equals, 2)
|
|
} else if s.versionAtLeast(2, 6) {
|
|
c.Assert(err.(*mgo.QueryError).Code, Equals, 17287)
|
|
} else {
|
|
c.Assert(err.(*mgo.QueryError).Code, Equals, 13097)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestQueryErrorNext(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
iter := coll.Find(M{"a": 1}).Select(M{"a": M{"b": 1}}).Iter()
|
|
|
|
var result struct{}
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
|
|
err = iter.Close()
|
|
c.Assert(err, ErrorMatches, ".*Unsupported projection option:.*")
|
|
c.Assert(err.(*mgo.QueryError).Message, Matches, ".*Unsupported projection option:.*")
|
|
// Oh, the dance of error codes. :-(
|
|
if s.versionAtLeast(3, 2) {
|
|
c.Assert(err.(*mgo.QueryError).Code, Equals, 2)
|
|
} else if s.versionAtLeast(2, 6) {
|
|
c.Assert(err.(*mgo.QueryError).Code, Equals, 17287)
|
|
} else {
|
|
c.Assert(err.(*mgo.QueryError).Code, Equals, 13097)
|
|
}
|
|
c.Assert(iter.Err(), Equals, err)
|
|
}
|
|
|
|
var indexTests = []struct {
|
|
index mgo.Index
|
|
expected M
|
|
}{{
|
|
mgo.Index{
|
|
Key: []string{"a"},
|
|
Background: true,
|
|
},
|
|
M{
|
|
"name": "a_1",
|
|
"key": M{"a": 1},
|
|
"ns": "mydb.mycoll",
|
|
"background": true,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"a", "-b"},
|
|
Unique: true,
|
|
DropDups: true,
|
|
},
|
|
M{
|
|
"name": "a_1_b_-1",
|
|
"key": M{"a": 1, "b": -1},
|
|
"ns": "mydb.mycoll",
|
|
"unique": true,
|
|
"dropDups": true,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"@loc_old"}, // Obsolete
|
|
Min: -500,
|
|
Max: 500,
|
|
Bits: 32,
|
|
},
|
|
M{
|
|
"name": "loc_old_2d",
|
|
"key": M{"loc_old": "2d"},
|
|
"ns": "mydb.mycoll",
|
|
"min": -500.0,
|
|
"max": 500.0,
|
|
"bits": 32,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"$2d:loc"},
|
|
Min: -500,
|
|
Max: 500,
|
|
Bits: 32,
|
|
},
|
|
M{
|
|
"name": "loc_2d",
|
|
"key": M{"loc": "2d"},
|
|
"ns": "mydb.mycoll",
|
|
"min": -500.0,
|
|
"max": 500.0,
|
|
"bits": 32,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"$2d:loc"},
|
|
Minf: -500.1,
|
|
Maxf: 500.1,
|
|
Min: 1, // Should be ignored
|
|
Max: 2,
|
|
Bits: 32,
|
|
},
|
|
M{
|
|
"name": "loc_2d",
|
|
"key": M{"loc": "2d"},
|
|
"ns": "mydb.mycoll",
|
|
"min": -500.1,
|
|
"max": 500.1,
|
|
"bits": 32,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"$geoHaystack:loc", "type"},
|
|
BucketSize: 1,
|
|
},
|
|
M{
|
|
"name": "loc_geoHaystack_type_1",
|
|
"key": M{"loc": "geoHaystack", "type": 1},
|
|
"ns": "mydb.mycoll",
|
|
"bucketSize": 1.0,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"$text:a", "$text:b"},
|
|
Weights: map[string]int{"b": 42},
|
|
},
|
|
M{
|
|
"name": "a_text_b_text",
|
|
"key": M{"_fts": "text", "_ftsx": 1},
|
|
"ns": "mydb.mycoll",
|
|
"weights": M{"a": 1, "b": 42},
|
|
"default_language": "english",
|
|
"language_override": "language",
|
|
"textIndexVersion": 2,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"$text:a"},
|
|
DefaultLanguage: "portuguese",
|
|
LanguageOverride: "idioma",
|
|
},
|
|
M{
|
|
"name": "a_text",
|
|
"key": M{"_fts": "text", "_ftsx": 1},
|
|
"ns": "mydb.mycoll",
|
|
"weights": M{"a": 1},
|
|
"default_language": "portuguese",
|
|
"language_override": "idioma",
|
|
"textIndexVersion": 2,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"$text:$**"},
|
|
},
|
|
M{
|
|
"name": "$**_text",
|
|
"key": M{"_fts": "text", "_ftsx": 1},
|
|
"ns": "mydb.mycoll",
|
|
"weights": M{"$**": 1},
|
|
"default_language": "english",
|
|
"language_override": "language",
|
|
"textIndexVersion": 2,
|
|
},
|
|
}, {
|
|
mgo.Index{
|
|
Key: []string{"cn"},
|
|
Name: "CustomName",
|
|
},
|
|
M{
|
|
"name": "CustomName",
|
|
"key": M{"cn": 1},
|
|
"ns": "mydb.mycoll",
|
|
},
|
|
}}
|
|
|
|
func (s *S) TestEnsureIndex(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
idxs := session.DB("mydb").C("system.indexes")
|
|
|
|
for _, test := range indexTests {
|
|
if !s.versionAtLeast(2, 4) && test.expected["textIndexVersion"] != nil {
|
|
continue
|
|
}
|
|
|
|
err = coll.EnsureIndex(test.index)
|
|
msg := "text search not enabled"
|
|
if err != nil && strings.Contains(err.Error(), msg) {
|
|
continue
|
|
}
|
|
c.Assert(err, IsNil)
|
|
|
|
expectedName := test.index.Name
|
|
if expectedName == "" {
|
|
expectedName, _ = test.expected["name"].(string)
|
|
}
|
|
|
|
obtained := M{}
|
|
err = idxs.Find(M{"name": expectedName}).One(obtained)
|
|
c.Assert(err, IsNil)
|
|
|
|
delete(obtained, "v")
|
|
|
|
if s.versionAtLeast(2, 7) {
|
|
// Was deprecated in 2.6, and not being reported by 2.7+.
|
|
delete(test.expected, "dropDups")
|
|
test.index.DropDups = false
|
|
}
|
|
if s.versionAtLeast(3, 2) && test.expected["textIndexVersion"] != nil {
|
|
test.expected["textIndexVersion"] = 3
|
|
}
|
|
|
|
c.Assert(obtained, DeepEquals, test.expected)
|
|
|
|
// The result of Indexes must match closely what was used to create the index.
|
|
indexes, err := coll.Indexes()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(indexes, HasLen, 2)
|
|
gotIndex := indexes[0]
|
|
if gotIndex.Name == "_id_" {
|
|
gotIndex = indexes[1]
|
|
}
|
|
wantIndex := test.index
|
|
if wantIndex.Name == "" {
|
|
wantIndex.Name = gotIndex.Name
|
|
}
|
|
if strings.HasPrefix(wantIndex.Key[0], "@") {
|
|
wantIndex.Key[0] = "$2d:" + wantIndex.Key[0][1:]
|
|
}
|
|
if wantIndex.Minf == 0 && wantIndex.Maxf == 0 {
|
|
wantIndex.Minf = float64(wantIndex.Min)
|
|
wantIndex.Maxf = float64(wantIndex.Max)
|
|
} else {
|
|
wantIndex.Min = gotIndex.Min
|
|
wantIndex.Max = gotIndex.Max
|
|
}
|
|
if wantIndex.DefaultLanguage == "" {
|
|
wantIndex.DefaultLanguage = gotIndex.DefaultLanguage
|
|
}
|
|
if wantIndex.LanguageOverride == "" {
|
|
wantIndex.LanguageOverride = gotIndex.LanguageOverride
|
|
}
|
|
for name, _ := range gotIndex.Weights {
|
|
if _, ok := wantIndex.Weights[name]; !ok {
|
|
if wantIndex.Weights == nil {
|
|
wantIndex.Weights = make(map[string]int)
|
|
}
|
|
wantIndex.Weights[name] = 1
|
|
}
|
|
}
|
|
c.Assert(gotIndex, DeepEquals, wantIndex)
|
|
|
|
// Drop created index by key or by name if a custom name was used.
|
|
if test.index.Name == "" {
|
|
err = coll.DropIndex(test.index.Key...)
|
|
c.Assert(err, IsNil)
|
|
} else {
|
|
err = coll.DropIndexName(test.index.Name)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexWithBadInfo(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndex(mgo.Index{})
|
|
c.Assert(err, ErrorMatches, "invalid index key:.*")
|
|
|
|
err = coll.EnsureIndex(mgo.Index{Key: []string{""}})
|
|
c.Assert(err, ErrorMatches, "invalid index key:.*")
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexWithUnsafeSession(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
session.SetSafe(nil)
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"a": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{"a": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
// Should fail since there are duplicated entries.
|
|
index := mgo.Index{
|
|
Key: []string{"a"},
|
|
Unique: true,
|
|
}
|
|
|
|
err = coll.EnsureIndex(index)
|
|
c.Assert(err, ErrorMatches, ".*duplicate key error.*")
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexKey(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.EnsureIndexKey("a", "-b")
|
|
c.Assert(err, IsNil)
|
|
|
|
sysidx := session.DB("mydb").C("system.indexes")
|
|
|
|
result1 := M{}
|
|
err = sysidx.Find(M{"name": "a_1"}).One(result1)
|
|
c.Assert(err, IsNil)
|
|
|
|
result2 := M{}
|
|
err = sysidx.Find(M{"name": "a_1_b_-1"}).One(result2)
|
|
c.Assert(err, IsNil)
|
|
|
|
delete(result1, "v")
|
|
expected1 := M{
|
|
"name": "a_1",
|
|
"key": M{"a": 1},
|
|
"ns": "mydb.mycoll",
|
|
}
|
|
c.Assert(result1, DeepEquals, expected1)
|
|
|
|
delete(result2, "v")
|
|
expected2 := M{
|
|
"name": "a_1_b_-1",
|
|
"key": M{"a": 1, "b": -1},
|
|
"ns": "mydb.mycoll",
|
|
}
|
|
c.Assert(result2, DeepEquals, expected2)
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexDropIndex(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.EnsureIndexKey("-b")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.DropIndex("-b")
|
|
c.Assert(err, IsNil)
|
|
|
|
sysidx := session.DB("mydb").C("system.indexes")
|
|
|
|
err = sysidx.Find(M{"name": "a_1"}).One(nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = sysidx.Find(M{"name": "b_1"}).One(nil)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.DropIndex("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = sysidx.Find(M{"name": "a_1"}).One(nil)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.DropIndex("a")
|
|
c.Assert(err, ErrorMatches, "index not found.*")
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexDropIndexName(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.EnsureIndex(mgo.Index{Key: []string{"b"}, Name: "a"})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.DropIndexName("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
sysidx := session.DB("mydb").C("system.indexes")
|
|
|
|
err = sysidx.Find(M{"name": "a_1"}).One(nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = sysidx.Find(M{"name": "a"}).One(nil)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.DropIndexName("a_1")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = sysidx.Find(M{"name": "a_1"}).One(nil)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
|
|
err = coll.DropIndexName("a_1")
|
|
c.Assert(err, ErrorMatches, "index not found.*")
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexCaching(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
mgo.ResetStats()
|
|
|
|
// Second EnsureIndex should be cached and do nothing.
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 0)
|
|
|
|
// Resetting the cache should make it contact the server again.
|
|
session.ResetIndexCache()
|
|
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
stats = mgo.GetStats()
|
|
c.Assert(stats.SentOps > 0, Equals, true)
|
|
|
|
// Dropping the index should also drop the cached index key.
|
|
err = coll.DropIndex("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
mgo.ResetStats()
|
|
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
stats = mgo.GetStats()
|
|
c.Assert(stats.SentOps > 0, Equals, true)
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexGetIndexes(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndexKey("-b")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
// Obsolete.
|
|
err = coll.EnsureIndexKey("@c")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.EnsureIndexKey("$2d:d")
|
|
c.Assert(err, IsNil)
|
|
|
|
// Try to exercise cursor logic. 2.8.0-rc3 still ignores this.
|
|
session.SetBatch(2)
|
|
|
|
indexes, err := coll.Indexes()
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(indexes[0].Name, Equals, "_id_")
|
|
c.Assert(indexes[1].Name, Equals, "a_1")
|
|
c.Assert(indexes[1].Key, DeepEquals, []string{"a"})
|
|
c.Assert(indexes[2].Name, Equals, "b_-1")
|
|
c.Assert(indexes[2].Key, DeepEquals, []string{"-b"})
|
|
c.Assert(indexes[3].Name, Equals, "c_2d")
|
|
c.Assert(indexes[3].Key, DeepEquals, []string{"$2d:c"})
|
|
c.Assert(indexes[4].Name, Equals, "d_2d")
|
|
c.Assert(indexes[4].Key, DeepEquals, []string{"$2d:d"})
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexNameCaching(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.EnsureIndex(mgo.Index{Key: []string{"a"}, Name: "custom"})
|
|
c.Assert(err, IsNil)
|
|
|
|
mgo.ResetStats()
|
|
|
|
// Second EnsureIndex should be cached and do nothing.
|
|
err = coll.EnsureIndexKey("a")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.EnsureIndex(mgo.Index{Key: []string{"a"}, Name: "custom"})
|
|
c.Assert(err, IsNil)
|
|
|
|
stats := mgo.GetStats()
|
|
c.Assert(stats.SentOps, Equals, 0)
|
|
|
|
// Resetting the cache should make it contact the server again.
|
|
session.ResetIndexCache()
|
|
|
|
err = coll.EnsureIndex(mgo.Index{Key: []string{"a"}, Name: "custom"})
|
|
c.Assert(err, IsNil)
|
|
|
|
stats = mgo.GetStats()
|
|
c.Assert(stats.SentOps > 0, Equals, true)
|
|
|
|
// Dropping the index should also drop the cached index key.
|
|
err = coll.DropIndexName("custom")
|
|
c.Assert(err, IsNil)
|
|
|
|
mgo.ResetStats()
|
|
|
|
err = coll.EnsureIndex(mgo.Index{Key: []string{"a"}, Name: "custom"})
|
|
c.Assert(err, IsNil)
|
|
|
|
stats = mgo.GetStats()
|
|
c.Assert(stats.SentOps > 0, Equals, true)
|
|
}
|
|
|
|
func (s *S) TestEnsureIndexEvalGetIndexes(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({b: -1})"}}, nil)
|
|
c.Assert(err, IsNil)
|
|
err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({a: 1})"}}, nil)
|
|
c.Assert(err, IsNil)
|
|
err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({c: -1, e: 1})"}}, nil)
|
|
c.Assert(err, IsNil)
|
|
err = session.Run(bson.D{{"eval", "db.getSiblingDB('mydb').mycoll.ensureIndex({d: '2d'})"}}, nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
indexes, err := coll.Indexes()
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(indexes[0].Name, Equals, "_id_")
|
|
c.Assert(indexes[1].Name, Equals, "a_1")
|
|
c.Assert(indexes[1].Key, DeepEquals, []string{"a"})
|
|
c.Assert(indexes[2].Name, Equals, "b_-1")
|
|
c.Assert(indexes[2].Key, DeepEquals, []string{"-b"})
|
|
c.Assert(indexes[3].Name, Equals, "c_-1_e_1")
|
|
c.Assert(indexes[3].Key, DeepEquals, []string{"-c", "e"})
|
|
if s.versionAtLeast(2, 2) {
|
|
c.Assert(indexes[4].Name, Equals, "d_2d")
|
|
c.Assert(indexes[4].Key, DeepEquals, []string{"$2d:d"})
|
|
} else {
|
|
c.Assert(indexes[4].Name, Equals, "d_")
|
|
c.Assert(indexes[4].Key, DeepEquals, []string{"$2d:d"})
|
|
}
|
|
}
|
|
|
|
var testTTL = flag.Bool("test-ttl", false, "test TTL collections (may take 1 minute)")
|
|
|
|
func (s *S) TestEnsureIndexExpireAfter(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
session.SetSafe(nil)
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
err = coll.Insert(M{"n": 1, "t": time.Now().Add(-120 * time.Second)})
|
|
c.Assert(err, IsNil)
|
|
err = coll.Insert(M{"n": 2, "t": time.Now()})
|
|
c.Assert(err, IsNil)
|
|
|
|
// Should fail since there are duplicated entries.
|
|
index := mgo.Index{
|
|
Key: []string{"t"},
|
|
ExpireAfter: 1 * time.Minute,
|
|
}
|
|
|
|
err = coll.EnsureIndex(index)
|
|
c.Assert(err, IsNil)
|
|
|
|
indexes, err := coll.Indexes()
|
|
c.Assert(err, IsNil)
|
|
c.Assert(indexes[1].Name, Equals, "t_1")
|
|
c.Assert(indexes[1].ExpireAfter, Equals, 1*time.Minute)
|
|
|
|
if *testTTL {
|
|
worked := false
|
|
stop := time.Now().Add(70 * time.Second)
|
|
for time.Now().Before(stop) {
|
|
n, err := coll.Count()
|
|
c.Assert(err, IsNil)
|
|
if n == 1 {
|
|
worked = true
|
|
break
|
|
}
|
|
c.Assert(n, Equals, 2)
|
|
c.Logf("Still has 2 entries...")
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
if !worked {
|
|
c.Fatalf("TTL index didn't work")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *S) TestDistinct(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
|
|
coll.Insert(M{"n": i})
|
|
}
|
|
|
|
var result []int
|
|
err = coll.Find(M{"n": M{"$gt": 2}}).Sort("n").Distinct("n", &result)
|
|
|
|
sort.IntSlice(result).Sort()
|
|
c.Assert(result, DeepEquals, []int{3, 4, 6})
|
|
}
|
|
|
|
func (s *S) TestMapReduce(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
|
|
coll.Insert(M{"n": i})
|
|
}
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, 1); }",
|
|
Reduce: "function(key, values) { return Array.sum(values); }",
|
|
}
|
|
var result []struct {
|
|
Id int "_id"
|
|
Value int
|
|
}
|
|
|
|
info, err := coll.Find(M{"n": M{"$gt": 2}}).MapReduce(job, &result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.InputCount, Equals, 4)
|
|
c.Assert(info.EmitCount, Equals, 4)
|
|
c.Assert(info.OutputCount, Equals, 3)
|
|
c.Assert(info.VerboseTime, IsNil)
|
|
|
|
expected := map[int]int{3: 1, 4: 2, 6: 1}
|
|
for _, item := range result {
|
|
c.Logf("Item: %#v", &item)
|
|
c.Assert(item.Value, Equals, expected[item.Id])
|
|
expected[item.Id] = -1
|
|
}
|
|
}
|
|
|
|
func (s *S) TestMapReduceFinalize(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
|
|
coll.Insert(M{"n": i})
|
|
}
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, 1) }",
|
|
Reduce: "function(key, values) { return Array.sum(values) }",
|
|
Finalize: "function(key, count) { return {count: count} }",
|
|
}
|
|
var result []struct {
|
|
Id int "_id"
|
|
Value struct{ Count int }
|
|
}
|
|
_, err = coll.Find(nil).MapReduce(job, &result)
|
|
c.Assert(err, IsNil)
|
|
|
|
expected := map[int]int{1: 1, 2: 2, 3: 1, 4: 2, 6: 1}
|
|
for _, item := range result {
|
|
c.Logf("Item: %#v", &item)
|
|
c.Assert(item.Value.Count, Equals, expected[item.Id])
|
|
expected[item.Id] = -1
|
|
}
|
|
}
|
|
|
|
func (s *S) TestMapReduceToCollection(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
|
|
coll.Insert(M{"n": i})
|
|
}
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, 1); }",
|
|
Reduce: "function(key, values) { return Array.sum(values); }",
|
|
Out: "mr",
|
|
}
|
|
|
|
info, err := coll.Find(nil).MapReduce(job, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.InputCount, Equals, 7)
|
|
c.Assert(info.EmitCount, Equals, 7)
|
|
c.Assert(info.OutputCount, Equals, 5)
|
|
c.Assert(info.Collection, Equals, "mr")
|
|
c.Assert(info.Database, Equals, "mydb")
|
|
|
|
expected := map[int]int{1: 1, 2: 2, 3: 1, 4: 2, 6: 1}
|
|
var item *struct {
|
|
Id int "_id"
|
|
Value int
|
|
}
|
|
mr := session.DB("mydb").C("mr")
|
|
iter := mr.Find(nil).Iter()
|
|
for iter.Next(&item) {
|
|
c.Logf("Item: %#v", &item)
|
|
c.Assert(item.Value, Equals, expected[item.Id])
|
|
expected[item.Id] = -1
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestMapReduceToOtherDb(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
|
|
coll.Insert(M{"n": i})
|
|
}
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, 1); }",
|
|
Reduce: "function(key, values) { return Array.sum(values); }",
|
|
Out: bson.D{{"replace", "mr"}, {"db", "otherdb"}},
|
|
}
|
|
|
|
info, err := coll.Find(nil).MapReduce(job, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.InputCount, Equals, 7)
|
|
c.Assert(info.EmitCount, Equals, 7)
|
|
c.Assert(info.OutputCount, Equals, 5)
|
|
c.Assert(info.Collection, Equals, "mr")
|
|
c.Assert(info.Database, Equals, "otherdb")
|
|
|
|
expected := map[int]int{1: 1, 2: 2, 3: 1, 4: 2, 6: 1}
|
|
var item *struct {
|
|
Id int "_id"
|
|
Value int
|
|
}
|
|
mr := session.DB("otherdb").C("mr")
|
|
iter := mr.Find(nil).Iter()
|
|
for iter.Next(&item) {
|
|
c.Logf("Item: %#v", &item)
|
|
c.Assert(item.Value, Equals, expected[item.Id])
|
|
expected[item.Id] = -1
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestMapReduceOutOfOrder(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
|
|
coll.Insert(M{"n": i})
|
|
}
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, 1); }",
|
|
Reduce: "function(key, values) { return Array.sum(values); }",
|
|
Out: bson.M{"a": "a", "z": "z", "replace": "mr", "db": "otherdb", "b": "b", "y": "y"},
|
|
}
|
|
|
|
info, err := coll.Find(nil).MapReduce(job, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Collection, Equals, "mr")
|
|
c.Assert(info.Database, Equals, "otherdb")
|
|
}
|
|
|
|
func (s *S) TestMapReduceScope(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
coll.Insert(M{"n": 1})
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, x); }",
|
|
Reduce: "function(key, values) { return Array.sum(values); }",
|
|
Scope: M{"x": 42},
|
|
}
|
|
|
|
var result []bson.M
|
|
_, err = coll.Find(nil).MapReduce(job, &result)
|
|
c.Assert(len(result), Equals, 1)
|
|
c.Assert(result[0]["value"], Equals, 42.0)
|
|
}
|
|
|
|
func (s *S) TestMapReduceVerbose(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for i := 0; i < 100; i++ {
|
|
err = coll.Insert(M{"n": i})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, 1); }",
|
|
Reduce: "function(key, values) { return Array.sum(values); }",
|
|
Verbose: true,
|
|
}
|
|
|
|
info, err := coll.Find(nil).MapReduce(job, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.VerboseTime, NotNil)
|
|
}
|
|
|
|
func (s *S) TestMapReduceLimit(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for _, i := range []int{1, 4, 6, 2, 2, 3, 4} {
|
|
coll.Insert(M{"n": i})
|
|
}
|
|
|
|
job := &mgo.MapReduce{
|
|
Map: "function() { emit(this.n, 1); }",
|
|
Reduce: "function(key, values) { return Array.sum(values); }",
|
|
}
|
|
|
|
var result []bson.M
|
|
_, err = coll.Find(nil).Limit(3).MapReduce(job, &result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(result), Equals, 3)
|
|
}
|
|
|
|
func (s *S) TestBuildInfo(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
info, err := session.BuildInfo()
|
|
c.Assert(err, IsNil)
|
|
|
|
var v []int
|
|
for i, a := range strings.Split(info.Version, ".") {
|
|
for _, token := range []string{"-rc", "-pre"} {
|
|
if i == 2 && strings.Contains(a, token) {
|
|
a = a[:strings.Index(a, token)]
|
|
info.VersionArray[len(info.VersionArray)-1] = 0
|
|
}
|
|
}
|
|
n, err := strconv.Atoi(a)
|
|
c.Assert(err, IsNil)
|
|
v = append(v, n)
|
|
}
|
|
for len(v) < 4 {
|
|
v = append(v, 0)
|
|
}
|
|
|
|
c.Assert(info.VersionArray, DeepEquals, v)
|
|
c.Assert(info.GitVersion, Matches, "[a-z0-9]+")
|
|
|
|
if s.versionAtLeast(3, 2) {
|
|
// It was deprecated in 3.2.
|
|
c.Assert(info.SysInfo, Equals, "")
|
|
} else {
|
|
c.Assert(info.SysInfo, Matches, ".*[0-9:]+.*")
|
|
}
|
|
if info.Bits != 32 && info.Bits != 64 {
|
|
c.Fatalf("info.Bits is %d", info.Bits)
|
|
}
|
|
if info.MaxObjectSize < 8192 {
|
|
c.Fatalf("info.MaxObjectSize seems too small: %d", info.MaxObjectSize)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestZeroTimeRoundtrip(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
var d struct{ T time.Time }
|
|
conn := session.DB("mydb").C("mycoll")
|
|
err = conn.Insert(d)
|
|
c.Assert(err, IsNil)
|
|
|
|
var result bson.M
|
|
err = conn.Find(nil).One(&result)
|
|
c.Assert(err, IsNil)
|
|
t, isTime := result["t"].(time.Time)
|
|
c.Assert(isTime, Equals, true)
|
|
c.Assert(t, Equals, time.Time{})
|
|
}
|
|
|
|
func (s *S) TestFsyncLock(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
clone := session.Clone()
|
|
defer clone.Close()
|
|
|
|
err = session.FsyncLock()
|
|
c.Assert(err, IsNil)
|
|
|
|
done := make(chan time.Time)
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
now := time.Now()
|
|
err := session.FsyncUnlock()
|
|
c.Check(err, IsNil)
|
|
done <- now
|
|
}()
|
|
|
|
err = clone.DB("mydb").C("mycoll").Insert(bson.M{"n": 1})
|
|
unlocked := time.Now()
|
|
unlocking := <-done
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(unlocked.After(unlocking), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestFsync(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
// Not much to do here. Just a smoke check.
|
|
err = session.Fsync(false)
|
|
c.Assert(err, IsNil)
|
|
err = session.Fsync(true)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *S) TestRepairCursor(c *C) {
|
|
if !s.versionAtLeast(2, 7) {
|
|
c.Skip("RepairCursor only works on 2.7+")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
session.SetBatch(2)
|
|
|
|
coll := session.DB("mydb").C("mycoll3")
|
|
err = coll.DropCollection()
|
|
|
|
ns := []int{0, 10, 20, 30, 40, 50}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
repairIter := coll.Repair()
|
|
|
|
c.Assert(repairIter.Err(), IsNil)
|
|
|
|
result := struct{ N int }{}
|
|
resultCounts := map[int]int{}
|
|
for repairIter.Next(&result) {
|
|
resultCounts[result.N]++
|
|
}
|
|
|
|
c.Assert(repairIter.Next(&result), Equals, false)
|
|
c.Assert(repairIter.Err(), IsNil)
|
|
c.Assert(repairIter.Close(), IsNil)
|
|
|
|
// Verify that the results of the repair cursor are valid.
|
|
// The repair cursor can return multiple copies
|
|
// of the same document, so to check correctness we only
|
|
// need to verify that at least 1 of each document was returned.
|
|
|
|
for _, key := range ns {
|
|
c.Assert(resultCounts[key] > 0, Equals, true)
|
|
}
|
|
}
|
|
|
|
func (s *S) TestPipeIter(c *C) {
|
|
if !s.versionAtLeast(2, 1) {
|
|
c.Skip("Pipe only works on 2.1+")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
pipe := coll.Pipe([]M{{"$match": M{"n": M{"$gte": 42}}}})
|
|
|
|
// Ensure cursor logic is working by forcing a small batch.
|
|
pipe.Batch(2)
|
|
|
|
// Smoke test for AllowDiskUse.
|
|
pipe.AllowDiskUse()
|
|
|
|
iter := pipe.Iter()
|
|
result := struct{ N int }{}
|
|
for i := 2; i < 7; i++ {
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.N, Equals, ns[i])
|
|
}
|
|
|
|
c.Assert(iter.Next(&result), Equals, false)
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestPipeAll(c *C) {
|
|
if !s.versionAtLeast(2, 1) {
|
|
c.Skip("Pipe only works on 2.1+")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err := coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
var result []struct{ N int }
|
|
err = coll.Pipe([]M{{"$match": M{"n": M{"$gte": 42}}}}).All(&result)
|
|
c.Assert(err, IsNil)
|
|
for i := 2; i < 7; i++ {
|
|
c.Assert(result[i-2].N, Equals, ns[i])
|
|
}
|
|
}
|
|
|
|
func (s *S) TestPipeOne(c *C) {
|
|
if !s.versionAtLeast(2, 1) {
|
|
c.Skip("Pipe only works on 2.1+")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
coll.Insert(M{"a": 1, "b": 2})
|
|
|
|
result := struct{ A, B int }{}
|
|
|
|
pipe := coll.Pipe([]M{{"$project": M{"a": 1, "b": M{"$add": []interface{}{"$b", 1}}}}})
|
|
err = pipe.One(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.A, Equals, 1)
|
|
c.Assert(result.B, Equals, 3)
|
|
|
|
pipe = coll.Pipe([]M{{"$match": M{"a": 2}}})
|
|
err = pipe.One(&result)
|
|
c.Assert(err, Equals, mgo.ErrNotFound)
|
|
}
|
|
|
|
func (s *S) TestPipeExplain(c *C) {
|
|
if !s.versionAtLeast(2, 1) {
|
|
c.Skip("Pipe only works on 2.1+")
|
|
}
|
|
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
coll.Insert(M{"a": 1, "b": 2})
|
|
|
|
pipe := coll.Pipe([]M{{"$project": M{"a": 1, "b": M{"$add": []interface{}{"$b", 1}}}}})
|
|
|
|
// The explain command result changes across versions.
|
|
var result struct{ Ok int }
|
|
err = pipe.Explain(&result)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(result.Ok, Equals, 1)
|
|
}
|
|
|
|
func (s *S) TestBatch1Bug(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for i := 0; i < 3; i++ {
|
|
err := coll.Insert(M{"n": i})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
var ns []struct{ N int }
|
|
err = coll.Find(nil).Batch(1).All(&ns)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(ns), Equals, 3)
|
|
|
|
session.SetBatch(1)
|
|
err = coll.Find(nil).All(&ns)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(ns), Equals, 3)
|
|
}
|
|
|
|
func (s *S) TestInterfaceIterBug(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
for i := 0; i < 3; i++ {
|
|
err := coll.Insert(M{"n": i})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
var result interface{}
|
|
|
|
i := 0
|
|
iter := coll.Find(nil).Sort("n").Iter()
|
|
for iter.Next(&result) {
|
|
c.Assert(result.(bson.M)["n"], Equals, i)
|
|
i++
|
|
}
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestFindIterCloseKillsCursor(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
cursors := serverCursorsOpen(session)
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
err = coll.Insert(M{"n": n})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
iter := coll.Find(nil).Batch(2).Iter()
|
|
c.Assert(iter.Next(bson.M{}), Equals, true)
|
|
|
|
c.Assert(iter.Close(), IsNil)
|
|
c.Assert(serverCursorsOpen(session), Equals, cursors)
|
|
}
|
|
|
|
func (s *S) TestFindIterDoneWithBatches(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
ns := []int{40, 41, 42, 43, 44, 45, 46}
|
|
for _, n := range ns {
|
|
coll.Insert(M{"n": n})
|
|
}
|
|
|
|
iter := coll.Find(M{"n": M{"$gte": 42}}).Sort("$natural").Prefetch(0).Batch(2).Iter()
|
|
result := struct{ N int }{}
|
|
for i := 2; i < 7; i++ {
|
|
// first check will be with pending local record;
|
|
// second will be with open cursor ID but no local
|
|
// records
|
|
c.Assert(iter.Done(), Equals, false)
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true, Commentf("err=%v", err))
|
|
}
|
|
|
|
c.Assert(iter.Done(), Equals, true)
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Close(), IsNil)
|
|
}
|
|
|
|
func (s *S) TestFindIterDoneErr(c *C) {
|
|
session, err := mgo.Dial("localhost:40002")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
iter := coll.Find(nil).Iter()
|
|
|
|
result := struct{}{}
|
|
ok := iter.Next(&result)
|
|
c.Assert(iter.Done(), Equals, true)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Err(), ErrorMatches, "unauthorized.*|not authorized.*")
|
|
}
|
|
|
|
func (s *S) TestFindIterDoneNotFound(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
|
|
result := struct{ A, B int }{}
|
|
iter := coll.Find(M{"a": 1}).Iter()
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
c.Assert(iter.Done(), Equals, true)
|
|
}
|
|
|
|
func (s *S) TestLogReplay(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
for i := 0; i < 5; i++ {
|
|
err = coll.Insert(M{"ts": time.Now()})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
iter := coll.Find(nil).LogReplay().Iter()
|
|
if s.versionAtLeast(2, 6) {
|
|
// This used to fail in 2.4. Now it's just a smoke test.
|
|
c.Assert(iter.Err(), IsNil)
|
|
} else {
|
|
c.Assert(iter.Next(bson.M{}), Equals, false)
|
|
c.Assert(iter.Err(), ErrorMatches, "no ts field in query")
|
|
}
|
|
}
|
|
|
|
func (s *S) TestSetCursorTimeout(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Insert(M{"n": 42})
|
|
|
|
// This is just a smoke test. Won't wait 10 minutes for an actual timeout.
|
|
|
|
session.SetCursorTimeout(0)
|
|
|
|
var result struct{ N int }
|
|
iter := coll.Find(nil).Iter()
|
|
c.Assert(iter.Next(&result), Equals, true)
|
|
c.Assert(result.N, Equals, 42)
|
|
c.Assert(iter.Next(&result), Equals, false)
|
|
}
|
|
|
|
func (s *S) TestNewIterNoServer(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
data, err := bson.Marshal(bson.M{"a": 1})
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
iter := coll.NewIter(nil, []bson.Raw{{3, data}}, 42, nil)
|
|
|
|
var result struct{ A int }
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.A, Equals, 1)
|
|
|
|
ok = iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
|
|
c.Assert(iter.Err(), ErrorMatches, "server not available")
|
|
}
|
|
|
|
func (s *S) TestNewIterNoServerPresetErr(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
data, err := bson.Marshal(bson.M{"a": 1})
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
iter := coll.NewIter(nil, []bson.Raw{{3, data}}, 42, fmt.Errorf("my error"))
|
|
|
|
var result struct{ A int }
|
|
ok := iter.Next(&result)
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(result.A, Equals, 1)
|
|
|
|
ok = iter.Next(&result)
|
|
c.Assert(ok, Equals, false)
|
|
|
|
c.Assert(iter.Err(), ErrorMatches, "my error")
|
|
}
|
|
|
|
func (s *S) TestBypassValidation(c *C) {
|
|
if !s.versionAtLeast(3, 2) {
|
|
c.Skip("validation supported on 3.2+")
|
|
}
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
err = coll.Insert(M{"n": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Database.Run(bson.D{
|
|
{"collMod", "mycoll"},
|
|
{"validator", M{"s": M{"$type": "string"}}},
|
|
}, nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Insert(M{"n": 2})
|
|
c.Assert(err, ErrorMatches, "Document failed validation")
|
|
|
|
err = coll.Update(M{"n": 1}, M{"n": 10})
|
|
c.Assert(err, ErrorMatches, "Document failed validation")
|
|
|
|
session.SetBypassValidation(true)
|
|
|
|
err = coll.Insert(M{"n": 3})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = coll.Update(M{"n": 3}, M{"n": 4})
|
|
c.Assert(err, IsNil)
|
|
|
|
// Ensure this still works. Shouldn't be affected.
|
|
err = coll.Remove(M{"n": 1})
|
|
c.Assert(err, IsNil)
|
|
|
|
var result struct{ N int }
|
|
var ns []int
|
|
iter := coll.Find(nil).Iter()
|
|
for iter.Next(&result) {
|
|
ns = append(ns, result.N)
|
|
}
|
|
c.Assert(iter.Err(), IsNil)
|
|
sort.Ints(ns)
|
|
c.Assert(ns, DeepEquals, []int{4})
|
|
}
|
|
|
|
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}},
|
|
}
|
|
for _, pair := range tests {
|
|
bi := mgo.BuildInfo{VersionArray: pair[0]}
|
|
c.Assert(bi.VersionAtLeast(pair[1]...), Equals, true)
|
|
|
|
bi = mgo.BuildInfo{VersionArray: pair[0]}
|
|
c.Assert(bi.VersionAtLeast(pair[0]...), Equals, true)
|
|
|
|
bi = mgo.BuildInfo{VersionArray: pair[1]}
|
|
c.Assert(bi.VersionAtLeast(pair[1]...), Equals, true)
|
|
|
|
bi = mgo.BuildInfo{VersionArray: pair[1]}
|
|
c.Assert(bi.VersionAtLeast(pair[0]...), Equals, false)
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Some benchmarks that require a running database.
|
|
|
|
func (s *S) BenchmarkFindIterRaw(c *C) {
|
|
session, err := mgo.Dial("localhost:40001")
|
|
c.Assert(err, IsNil)
|
|
defer session.Close()
|
|
|
|
coll := session.DB("mydb").C("mycoll")
|
|
doc := bson.D{
|
|
{"f2", "a short string"},
|
|
{"f3", bson.D{{"1", "one"}, {"2", 2.0}}},
|
|
{"f4", []string{"a", "b", "c", "d", "e", "f", "g"}},
|
|
}
|
|
|
|
for i := 0; i < c.N+1; i++ {
|
|
err := coll.Insert(doc)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
session.SetBatch(c.N)
|
|
|
|
var raw bson.Raw
|
|
iter := coll.Find(nil).Iter()
|
|
iter.Next(&raw)
|
|
c.ResetTimer()
|
|
i := 0
|
|
for iter.Next(&raw) {
|
|
i++
|
|
}
|
|
c.StopTimer()
|
|
c.Assert(iter.Err(), IsNil)
|
|
c.Assert(i, Equals, c.N)
|
|
}
|