From d1e7ea16ee25b41525a3e20c2e0bac9e2cfce11b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Nieto?= <jose.carlos@menteslibres.net>
Date: Sun, 26 Jul 2015 07:33:35 -0500
Subject: [PATCH] MongoDB: Improving debug log.

---
 mongo/collection.go | 146 ++++++++-------------
 mongo/database.go   |  60 ++++-----
 mongo/result.go     | 304 +++++++++++++++++++++++++++++---------------
 3 files changed, 279 insertions(+), 231 deletions(-)

diff --git a/mongo/collection.go b/mongo/collection.go
index a4adaddd..223e6ebd 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 8d2da724..e7b3cabe 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 8d85e924..70823c8c 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
 }
-- 
GitLab