diff --git a/mongo/collection.go b/mongo/collection.go index a4adadddc9b1030f1d47893123ee34a2c3b47cee..223e6ebd91137fa7a9b98ab1782f96668c73b651 100644 --- a/mongo/collection.go +++ b/mongo/collection.go @@ -33,7 +33,7 @@ import ( "upper.io/db" ) -// Mongodb Collection +// Collection represents a mongodb collection. type Collection struct { name string parent *Source @@ -49,8 +49,15 @@ type chunks struct { GroupBy []interface{} } -func (self *Collection) Find(terms ...interface{}) db.Result { +var ( + // idCache should be a struct if we're going to cache more than just + // _id field here + idCache = make(map[reflect.Type]string, 0) + idCacheMutex sync.RWMutex +) +// Find creates a result set with the given conditions. +func (col *Collection) Find(terms ...interface{}) db.Result { queryChunks := &chunks{} // No specific fields given. @@ -58,23 +65,19 @@ func (self *Collection) Find(terms ...interface{}) db.Result { queryChunks.Fields = []string{"*"} } - queryChunks.Conditions = self.compileQuery(terms...) - - if debugEnabled() == true { - debugLogQuery(queryChunks) - } + queryChunks.Conditions = col.compileQuery(terms...) // Actually executing query. - result := &Result{ - self, - queryChunks, - nil, + r := &result{ + c: col, + queryChunks: queryChunks, } - return result + return r } -// Transforms conditions into something *mgo.Session can understand. +// compileStatement transforms conditions into something *mgo.Session can +// understand. func compileStatement(cond db.Cond) bson.M { conds := bson.M{} @@ -118,14 +121,15 @@ func compileStatement(cond db.Cond) bson.M { return conds } -// Compiles terms into something *mgo.Session can understand. -func (self *Collection) compileConditions(term interface{}) interface{} { +// compileConditions compiles terms into something *mgo.Session can +// understand. +func (col *Collection) compileConditions(term interface{}) interface{} { switch t := term.(type) { case []interface{}: values := []interface{}{} - for i, _ := range t { - value := self.compileConditions(t[i]) + for i := range t { + value := col.compileConditions(t[i]) if value != nil { values = append(values, value) } @@ -135,15 +139,15 @@ func (self *Collection) compileConditions(term interface{}) interface{} { } case db.Or: values := []interface{}{} - for i, _ := range t { - values = append(values, self.compileConditions(t[i])) + for i := range t { + values = append(values, col.compileConditions(t[i])) } condition := bson.M{`$or`: values} return condition case db.And: values := []interface{}{} - for i, _ := range t { - values = append(values, self.compileConditions(t[i])) + for i := range t { + values = append(values, col.compileConditions(t[i])) } condition := bson.M{`$and`: values} return condition @@ -151,17 +155,16 @@ func (self *Collection) compileConditions(term interface{}) interface{} { return compileStatement(t) case db.Constrainer: return compileStatement(t.Constraint()) - //default: - // panic(fmt.Sprintf(db.ErrUnknownConditionType.Error(), reflect.TypeOf(t))) } return nil } -// Compiles terms into something that *mgo.Session can understand. -func (self *Collection) compileQuery(terms ...interface{}) interface{} { +// compileQuery compiles terms into something that *mgo.Session can +// understand. +func (col *Collection) compileQuery(terms ...interface{}) interface{} { var query interface{} - compiled := self.compileConditions(terms) + compiled := col.compileConditions(terms) if compiled != nil { conditions := compiled.([]interface{}) @@ -171,10 +174,10 @@ func (self *Collection) compileQuery(terms ...interface{}) interface{} { // this should be correct. // query = map[string]interface{}{"$and": conditions} - // trying to workaround https://jira.mongodb.org/browse/SERVER-4572 + // attempt to workaround https://jira.mongodb.org/browse/SERVER-4572 mapped := map[string]interface{}{} for _, v := range conditions { - for kk, _ := range v.(map[string]interface{}) { + for kk := range v.(map[string]interface{}) { mapped[kk] = v.(map[string]interface{})[kk] } } @@ -188,13 +191,14 @@ func (self *Collection) compileQuery(terms ...interface{}) interface{} { return query } -func (self *Collection) Name() string { - return self.collection.Name +// Name returns the name of the table or tables that form the collection. +func (col *Collection) Name() string { + return col.collection.Name } -// Deletes all the rows within the collection. -func (self *Collection) Truncate() error { - err := self.collection.DropCollection() +// Truncate deletes all rows from the table. +func (col *Collection) Truncate() error { + err := col.collection.DropCollection() if err != nil { return err @@ -203,27 +207,27 @@ func (self *Collection) Truncate() error { return nil } -// Appends an item (map or struct) into the collection. -func (self *Collection) Append(item interface{}) (interface{}, error) { +// Append inserts an item (map or struct) into the collection. +func (col *Collection) Append(item interface{}) (interface{}, error) { var err error - id := getId(item) + id := getID(item) - if self.parent.VersionAtLeast(2, 6, 0, 0) { + if col.parent.versionAtLeast(2, 6, 0, 0) { // this breaks MongoDb older than 2.6 - if _, err = self.collection.Upsert(bson.M{"_id": id}, item); err != nil { + if _, err = col.collection.Upsert(bson.M{"_id": id}, item); err != nil { return nil, err } } else { // Allocating a new ID. - if err = self.collection.Insert(bson.M{"_id": id}); err != nil { + if err = col.collection.Insert(bson.M{"_id": id}); err != nil { return nil, err } // Now append data the user wants to append. - if err = self.collection.Update(bson.M{"_id": id}, item); err != nil { + if err = col.collection.Update(bson.M{"_id": id}, item); err != nil { // Cleanup allocated ID - self.collection.Remove(bson.M{"_id": id}) + col.collection.Remove(bson.M{"_id": id}) return nil, err } } @@ -245,9 +249,9 @@ func (self *Collection) Append(item interface{}) (interface{}, error) { return id, nil } -// Returns true if the collection exists. -func (self *Collection) Exists() bool { - query := self.parent.database.C(`system.namespaces`).Find(map[string]string{`name`: fmt.Sprintf(`%s.%s`, self.parent.database.Name, self.collection.Name)}) +// Exists returns true if the collection exists. +func (col *Collection) Exists() bool { + query := col.parent.database.C(`system.namespaces`).Find(map[string]string{`name`: fmt.Sprintf(`%s.%s`, col.parent.database.Name, col.collection.Name)}) count, _ := query.Count() if count > 0 { return true @@ -255,59 +259,17 @@ func (self *Collection) Exists() bool { return false } -// Transforms data from db.Item format into mgo format. -func toInternal(val interface{}) interface{} { - - // TODO: use reflection to target kinds and not just types. - switch t := val.(type) { - case db.Cond: - for k, _ := range t { - t[k] = toInternal(t[k]) - } - case map[string]interface{}: - for k, _ := range t { - t[k] = toInternal(t[k]) - } - } - - return val -} - -// Transforms data from mgo format into db.Item format. -func toNative(val interface{}) interface{} { - - // TODO: use reflection to target kinds and not just types. - - switch t := val.(type) { - case bson.M: - v := map[string]interface{}{} - for i, _ := range t { - v[i] = toNative(t[i]) - } - return v - } - - return val - -} - -var ( - // idCache should be a struct if we're going to cache more than just _id field here - idCache = make(map[reflect.Type]string, 0) - idCacheMutex sync.RWMutex -) - // Fetches object _id or generates a new one if object doesn't have one or the one it has is invalid -func getId(item interface{}) bson.ObjectId { +func getID(item interface{}) bson.ObjectId { v := reflect.ValueOf(item) switch v.Kind() { case reflect.Map: if inItem, ok := item.(map[string]interface{}); ok { if id, ok := inItem["_id"]; ok { - bsonId, ok := id.(bson.ObjectId) + bsonID, ok := id.(bson.ObjectId) if ok { - return bsonId + return bsonID } } } @@ -346,9 +308,9 @@ func getId(item interface{}) bson.ObjectId { } } if fieldName != "" { - if bsonId, ok := v.FieldByName(fieldName).Interface().(bson.ObjectId); ok { - if bsonId.Valid() { - return bsonId + if bsonID, ok := v.FieldByName(fieldName).Interface().(bson.ObjectId); ok { + if bsonID.Valid() { + return bsonID } } } diff --git a/mongo/database.go b/mongo/database.go index 8d2da72453502300a55704c58f0def69f2590874..e7b3cabef1f6a1f6a37f5cb2de2cb2fb0804a7ce 100644 --- a/mongo/database.go +++ b/mongo/database.go @@ -23,8 +23,6 @@ package mongo // import "upper.io/db/mongo" import ( "fmt" - "log" - "os" "strings" "time" @@ -32,10 +30,12 @@ import ( "upper.io/db" ) +// Adapter holds the name of the mongodb adapter. const Adapter = `mongo` var connTimeout = time.Second * 5 +// Source represents a MongoDB database. type Source struct { name string connURL db.ConnectionURL @@ -44,32 +44,22 @@ type Source struct { version []int } -func debugEnabled() bool { - if os.Getenv(db.EnvEnableDebug) != "" { - return true - } - return false -} - func init() { db.Register(Adapter, &Source{}) } -func debugLogQuery(c *chunks) { - log.Printf("Fields: %v\nLimit: %v\nOffset: %v\nSort: %v\nConditions: %v\n", c.Fields, c.Limit, c.Offset, c.Sort, c.Conditions) -} - -// Returns the string name of the database. +// Name returns the name of the database. func (s *Source) Name() string { return s.name } -// Stores database settings. +// Setup stores database settings and opens a connection to a database. func (s *Source) Setup(connURL db.ConnectionURL) error { s.connURL = connURL return s.Open() } +// Clone returns a cloned db.Database session. func (s *Source) Clone() (db.Database, error) { clone := &Source{ name: s.name, @@ -81,42 +71,47 @@ func (s *Source) Clone() (db.Database, error) { return clone, nil } +// Transaction should support transactions, but it doesn't as MongoDB +// currently does not support them. func (s *Source) Transaction() (db.Tx, error) { return nil, db.ErrUnsupported } +// Ping checks whether a connection to the database is still alive by pinging +// it, establishing a connection if necessary. func (s *Source) Ping() error { return s.session.Ping() } -// Returns the underlying *mgo.Session instance. +// Driver returns the underlying *sqlx.DB instance. func (s *Source) Driver() interface{} { return s.session } -// Attempts to connect to a database using the stored settings. +// Open attempts to connect to the database server using already stored +// settings. func (s *Source) Open() error { var err error // Before db.ConnectionURL we used a unified db.Settings struct. This // condition checks for that type and provides backwards compatibility. if settings, ok := s.connURL.(db.Settings); ok { - var sAddr string + var addr string if settings.Host != "" { if settings.Port > 0 { - sAddr = fmt.Sprintf("%s:%d", settings.Host, settings.Port) + addr = fmt.Sprintf("%s:%d", settings.Host, settings.Port) } else { - sAddr = settings.Host + addr = settings.Host } } else { - sAddr = settings.Socket + addr = settings.Socket } conn := ConnectionURL{ User: settings.User, Password: settings.Password, - Address: db.ParseAddress(sAddr), + Address: db.ParseAddress(addr), Database: settings.Database, } @@ -133,7 +128,7 @@ func (s *Source) Open() error { return nil } -// Closes the current database session. +// Close terminates the current database session. func (s *Source) Close() error { if s.session != nil { s.session.Close() @@ -141,7 +136,7 @@ func (s *Source) Close() error { return nil } -// Changes the active database. +// Use changes the active database. func (s *Source) Use(database string) (err error) { var conn ConnectionURL @@ -156,14 +151,13 @@ func (s *Source) Use(database string) (err error) { return s.Open() } -// Drops the currently active database. +// Drop drops the current database. func (s *Source) Drop() error { err := s.database.DropDatabase() return err } -// Returns a slice of non-system collection names within the active -// database. +// Collections returns a list of non-system tables from the database. func (s *Source) Collections() (cols []string, err error) { var rawcols []string var col string @@ -175,7 +169,7 @@ func (s *Source) Collections() (cols []string, err error) { cols = make([]string, 0, len(rawcols)) for _, col = range rawcols { - if strings.HasPrefix(col, "system.") == false { + if !strings.HasPrefix(col, "system.") { cols = append(cols, col) } } @@ -183,7 +177,7 @@ func (s *Source) Collections() (cols []string, err error) { return cols, nil } -// Returns a collection instance by name. +// Collection returns a collection by name. func (s *Source) Collection(names ...string) (db.Collection, error) { var err error @@ -197,14 +191,14 @@ func (s *Source) Collection(names ...string) (db.Collection, error) { col.parent = s col.collection = s.database.C(name) - if col.Exists() == false { + if !col.Exists() { err = db.ErrCollectionDoesNotExist } return col, err } -func (s *Source) VersionAtLeast(version ...int) bool { +func (s *Source) versionAtLeast(version ...int) bool { // only fetch this once - it makes a db call if len(s.version) == 0 { buildInfo, err := s.database.Session.BuildInfo() @@ -221,10 +215,6 @@ func (s *Source) VersionAtLeast(version ...int) bool { if s.version[i] < version[i] { return false } - - if s.version[i] > version[i] { - return true - } } return true } diff --git a/mongo/result.go b/mongo/result.go index 8d85e92464b33995763ce79a2f6cb130fc795498..70823c8cce702663035e3ce9d466770d5529db0a 100644 --- a/mongo/result.go +++ b/mongo/result.go @@ -1,38 +1,42 @@ -/* - Copyright (c) 2014 The upper.io/db authors. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +// Copyright (c) 2012-2015 The upper.io/db authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package mongo import ( "errors" "fmt" + "strings" + "time" + + "encoding/json" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" "upper.io/db" + "upper.io/db/util/sqlutil" ) -type Result struct { +// result represents a query result. +type result struct { c *Collection queryChunks *chunks iter *mgo.Iter @@ -42,113 +46,128 @@ var ( errUnknownSortValue = errors.New(`Unknown sort value "%s".`) ) -// Creates a *mgo.Iter we can use in Next(), All() or One(). -func (self *Result) setCursor() error { - if self.iter == nil { - q, err := self.query() +// setCursor creates a *mgo.Iter we can use in Next(), All() or One(). +func (r *result) setCursor() error { + if r.iter == nil { + q, err := r.query() if err != nil { return err } - self.iter = q.Iter() + r.iter = q.Iter() } return nil } -func (self *Result) Where(terms ...interface{}) db.Result { - self.queryChunks.Conditions = self.c.compileQuery(terms...) - return self +func (r *result) Where(terms ...interface{}) db.Result { + r.queryChunks.Conditions = r.c.compileQuery(terms...) + return r } -// Determines the maximum limit of results to be returned. -func (self *Result) Limit(n uint) db.Result { - self.queryChunks.Limit = int(n) - return self +// Limit determines the maximum limit of results to be returned. +func (r *result) Limit(n uint) db.Result { + r.queryChunks.Limit = int(n) + return r } -// Determines how many documents will be skipped before starting to grab +// Skip determines how many documents will be skipped before starting to grab // results. -func (self *Result) Skip(n uint) db.Result { - self.queryChunks.Offset = int(n) - return self +func (r *result) Skip(n uint) db.Result { + r.queryChunks.Offset = int(n) + return r } -// Determines sorting of results according to the provided names. Fields may be -// prefixed by - (minus) which means descending order, ascending order would be -// used otherwise. -func (self *Result) Sort(fields ...interface{}) db.Result { +// Sort determines sorting of results according to the provided names. Fields +// may be prefixed by - (minus) which means descending order, ascending order +// would be used otherwise. +func (r *result) Sort(fields ...interface{}) db.Result { ss := make([]string, len(fields)) for i, field := range fields { ss[i] = fmt.Sprintf(`%v`, field) } - self.queryChunks.Sort = ss - return self + r.queryChunks.Sort = ss + return r } -// Retrieves only the given fields. -func (self *Result) Select(fields ...interface{}) db.Result { +// Select marks the specific fields the user wants to retrieve. +func (r *result) Select(fields ...interface{}) db.Result { fieldslen := len(fields) - self.queryChunks.Fields = make([]string, 0, fieldslen) + r.queryChunks.Fields = make([]string, 0, fieldslen) for i := 0; i < fieldslen; i++ { - self.queryChunks.Fields = append(self.queryChunks.Fields, fmt.Sprintf(`%v`, fields[i])) + r.queryChunks.Fields = append(r.queryChunks.Fields, fmt.Sprintf(`%v`, fields[i])) } - return self + return r } -// Dumps all results into a pointer to an slice of structs or maps. -func (self *Result) All(dst interface{}) error { +// All dumps all results into a pointer to an slice of structs or maps. +func (r *result) All(dst interface{}) (err error) { - var err error + if db.Debug { + var start, end int64 + start = time.Now().UnixNano() + defer func() { + end = time.Now().UnixNano() + sqlutil.Log(r.debugQuery(fmt.Sprintf("find(%s)", mustJSON(r.queryChunks.Conditions))), nil, err, start, end) + }() + } - err = self.setCursor() + err = r.setCursor() if err != nil { return err } - err = self.iter.All(dst) + err = r.iter.All(dst) if err != nil { return err } - self.Close() + r.Close() return nil } -// Used to group results that have the same value in the same column or -// columns. -func (self *Result) Group(fields ...interface{}) db.Result { - self.queryChunks.GroupBy = fields - return self +// Group is used to group results that have the same value in the same column +// or columns. +func (r *result) Group(fields ...interface{}) db.Result { + r.queryChunks.GroupBy = fields + return r } -// Fetches only one result from the resultset. -func (self *Result) One(dst interface{}) error { - var err error - err = self.Next(dst) +// One fetches only one result from the resultset. +func (r *result) One(dst interface{}) (err error) { + if db.Debug { + var start, end int64 + start = time.Now().UnixNano() + defer func() { + end = time.Now().UnixNano() + sqlutil.Log(r.debugQuery(fmt.Sprintf("findOne(%s)", mustJSON(r.queryChunks.Conditions))), nil, err, start, end) + }() + } + + err = r.Next(dst) if err != nil { return err } - self.Close() + r.Close() return nil } -// Fetches the next result from the resultset. -func (self *Result) Next(dst interface{}) error { - err := self.setCursor() +// Next fetches the next result from the resultset. +func (r *result) Next(dst interface{}) error { + err := r.setCursor() if err != nil { return err } - success := self.iter.Next(dst) + success := r.iter.Next(dst) if success == false { - err := self.iter.Err() + err := r.iter.Err() if err == nil { return db.ErrNoMoreRows } @@ -158,75 +177,152 @@ func (self *Result) Next(dst interface{}) error { return nil } -// Removes the matching items from the collection. -func (self *Result) Remove() error { - var err error - _, err = self.c.collection.RemoveAll(self.queryChunks.Conditions) +// Remove deletes the matching items from the collection. +func (r *result) Remove() (err error) { + if db.Debug { + var start, end int64 + start = time.Now().UnixNano() + defer func() { + end = time.Now().UnixNano() + sqlutil.Log(r.debugQuery(fmt.Sprintf("remove(%s)", mustJSON(r.queryChunks.Conditions))), nil, err, start, end) + }() + } + + _, err = r.c.collection.RemoveAll(r.queryChunks.Conditions) if err != nil { return err } return nil } -// Closes the result set. -func (self *Result) Close() error { +// Close closes the result set. +func (r *result) Close() error { var err error - if self.iter != nil { - err = self.iter.Close() - self.iter = nil + if r.iter != nil { + err = r.iter.Close() + r.iter = nil } return err } -// Updates matching items from the collection with values of the given map or -// struct. -func (self *Result) Update(src interface{}) error { - var err error - _, err = self.c.collection.UpdateAll(self.queryChunks.Conditions, map[string]interface{}{"$set": src}) +// Update modified matching items from the collection with values of the given +// map or struct. +func (r *result) Update(src interface{}) (err error) { + updateSet := map[string]interface{}{"$set": src} + + if db.Debug { + var start, end int64 + start = time.Now().UnixNano() + defer func() { + end = time.Now().UnixNano() + sqlutil.Log(r.debugQuery(fmt.Sprintf("update(%s, %s)", mustJSON(r.queryChunks.Conditions), mustJSON(updateSet))), nil, err, start, end) + }() + } + + _, err = r.c.collection.UpdateAll(r.queryChunks.Conditions, updateSet) if err != nil { return err } return nil } -func (self *Result) query() (*mgo.Query, error) { +// query executes a mgo query. +func (r *result) query() (*mgo.Query, error) { var err error - q := self.c.collection.Find(self.queryChunks.Conditions) + q := r.c.collection.Find(r.queryChunks.Conditions) - if self.queryChunks.GroupBy != nil { + if len(r.queryChunks.GroupBy) > 0 { return nil, db.ErrUnsupported } - if self.queryChunks.Offset > 0 { - q = q.Skip(self.queryChunks.Offset) + if r.queryChunks.Offset > 0 { + q = q.Skip(r.queryChunks.Offset) } - if self.queryChunks.Limit > 0 { - q = q.Limit(self.queryChunks.Limit) + if r.queryChunks.Limit > 0 { + q = q.Limit(r.queryChunks.Limit) } - if self.queryChunks.Fields != nil { - sel := bson.M{} - for _, field := range self.queryChunks.Fields { + if len(r.queryChunks.Fields) > 0 { + selectedFields := bson.M{} + for _, field := range r.queryChunks.Fields { if field == `*` { break } - sel[field] = true + selectedFields[field] = true + } + if len(selectedFields) > 0 { + q = q.Select(selectedFields) } - q = q.Select(sel) } - if len(self.queryChunks.Sort) > 0 { - q.Sort(self.queryChunks.Sort...) + if len(r.queryChunks.Sort) > 0 { + q.Sort(r.queryChunks.Sort...) } return q, err } -// Counts matching elements. -func (self *Result) Count() (uint64, error) { - q := self.c.collection.Find(self.queryChunks.Conditions) - total, err := q.Count() - return uint64(total), err +// Count counts matching elements. +func (r *result) Count() (total uint64, err error) { + if db.Debug { + var start, end int64 + start = time.Now().UnixNano() + defer func() { + end = time.Now().UnixNano() + sqlutil.Log(r.debugQuery(fmt.Sprintf("find(%s).count()", mustJSON(r.queryChunks.Conditions))), nil, err, start, end) + }() + } + + q := r.c.collection.Find(r.queryChunks.Conditions) + var c int + c, err = q.Count() + return uint64(c), err +} + +func (r *result) debugQuery(action string) string { + query := fmt.Sprintf("db.%s.%s", r.c.collection.Name, action) + + if r.queryChunks.Limit > 0 { + query = fmt.Sprintf("%s.limit(%d)", query, r.queryChunks.Limit) + } + if r.queryChunks.Offset > 0 { + query = fmt.Sprintf("%s.offset(%d)", query, r.queryChunks.Offset) + } + if len(r.queryChunks.Fields) > 0 { + selectedFields := bson.M{} + for _, field := range r.queryChunks.Fields { + if field == `*` { + break + } + selectedFields[field] = true + } + if len(selectedFields) > 0 { + query = fmt.Sprintf("%s.select(%v)", query, selectedFields) + } + } + if len(r.queryChunks.GroupBy) > 0 { + escaped := make([]string, len(r.queryChunks.GroupBy)) + for i := range r.queryChunks.GroupBy { + escaped[i] = string(mustJSON(r.queryChunks.GroupBy[i])) + } + query = fmt.Sprintf("%s.groupBy(%v)", query, strings.Join(escaped, ", ")) + } + if len(r.queryChunks.Sort) > 0 { + escaped := make([]string, len(r.queryChunks.Sort)) + for i := range r.queryChunks.Sort { + escaped[i] = string(mustJSON(r.queryChunks.Sort[i])) + } + query = fmt.Sprintf("%s.sort(%s)", query, strings.Join(escaped, ", ")) + } + return query +} + +func mustJSON(in interface{}) (out []byte) { + out, err := json.Marshal(in) + if err != nil { + panic(err) + } + return out }