From 0354d52afd95067cba966be3710e457b52ff7c94 Mon Sep 17 00:00:00 2001 From: Carlos Nieto <jose.carlos@menteslibres.net> Date: Sat, 2 Nov 2013 06:52:45 -0600 Subject: [PATCH] Updating mongo wrapper to comply with the latest API definition. --- mongo/collection.go | 267 +++--------- mongo/database.go | 2 +- mongo/database_test.go | 919 ++++++++++++++++++----------------------- mongo/result.go | 141 +++++-- 4 files changed, 586 insertions(+), 743 deletions(-) diff --git a/mongo/collection.go b/mongo/collection.go index 87cbf94c..ccd6cdc1 100644 --- a/mongo/collection.go +++ b/mongo/collection.go @@ -1,5 +1,5 @@ /* - Copyright (c) 2012-2013 José Carlos Nieto, http://xiam.menteslibres.org/ + Copyright (c) 2012-2013 José Carlos Nieto, https://menteslibres.net/xiam Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -27,39 +27,33 @@ import ( "fmt" "labix.org/v2/mgo" "labix.org/v2/mgo/bson" + "strings" "upper.io/db" "upper.io/db/util" - "menteslibres.net/gosexy/to" - "strings" ) // Mongodb Collection -type SourceCollection struct { +type Collection struct { name string parent *Source collection *mgo.Collection util.C } -func (self *SourceCollection) Query(terms ...interface{}) (db.Result, error) { +type chunks struct { + Fields []string + Limit int + Offset int + Sort *db.Sort + Conditions interface{} +} - queryChunks := struct { - Fields []string - Limit int - Offset int - Sort *db.Sort - Relate db.Relate - RelateAll db.RelateAll - Relations []db.Relation - Conditions interface{} - }{} +func (self *Collection) Filter(terms ...interface{}) (db.Result, error) { - queryChunks.Relate = make(db.Relate) - queryChunks.RelateAll = make(db.RelateAll) + queryChunks := &chunks{} // Analyzing given terms. for _, term := range terms { - switch v := term.(type) { case db.Limit: queryChunks.Limit = int(v) @@ -69,14 +63,6 @@ func (self *SourceCollection) Query(terms ...interface{}) (db.Result, error) { queryChunks.Offset = int(v) case db.Fields: queryChunks.Fields = append(queryChunks.Fields, v...) - case db.Relate: - for name, terms := range v { - queryChunks.Relations = append(queryChunks.Relations, db.Relation{All: false, Name: name, Collection: nil, On: terms}) - } - case db.RelateAll: - for name, terms := range v { - queryChunks.Relations = append(queryChunks.Relations, db.Relation{All: true, Name: name, Collection: nil, On: terms}) - } } } @@ -85,14 +71,13 @@ func (self *SourceCollection) Query(terms ...interface{}) (db.Result, error) { queryChunks.Fields = []string{"*"} } - // Actually executing query. - q := self.buildQuery(terms...) + queryChunks.Conditions = self.compileQuery(terms...) + // Actually executing query. result := &Result{ - query: q, - collection: &self.C, - relations: queryChunks.Relations, - iter: q.Iter(), + self, + queryChunks, + nil, } return result, nil @@ -103,20 +88,20 @@ func compileStatement(where db.Cond) bson.M { conds := bson.M{} for key, val := range where { - key = strings.Trim(key, " ") - chunks := strings.SplitN(key, " ", 2) + key = strings.Trim(key, ` `) + chunks := strings.SplitN(key, ` `, 2) if len(chunks) > 1 { op := "" switch chunks[1] { - case ">": - op = "$gt" - case "<": - op = "$gt" - case "<=": - op = "$lte" - case ">=": - op = "$gte" + case `>`: + op = `$gt` + case `<`: + op = `$gt` + case `<=`: + op = `$lte` + case `>=`: + op = `$gte` default: op = chunks[1] } @@ -130,54 +115,8 @@ func compileStatement(where db.Cond) bson.M { return conds } -/* - Deletes the whole collection. -*/ -func (self *SourceCollection) Truncate() error { - err := self.collection.DropCollection() - - if err != nil { - return err - } - - return nil -} - -/* - Returns true if the collection exists. -*/ -func (self *SourceCollection) Exists() bool { - query := self.parent.database.C("system.namespaces").Find(db.Item{"name": fmt.Sprintf("%s.%s", self.parent.Name(), self.Name())}) - count, _ := query.Count() - if count > 0 { - return true - } - return false -} - -/* - Appends items to the collection. An item could be either a map or a struct. -*/ -func (self *SourceCollection) Append(items ...interface{}) ([]db.Id, error) { - var id db.Id - ids := make([]db.Id, len(items)) - for i, item := range items { - id = "" - // Dirty trick to return the Id with ease. - res, err := self.collection.Upsert(bson.M{"_id": nil}, toInternal(item)) - if err != nil { - return ids, err - } - if res.UpsertedId != nil { - id = db.Id(res.UpsertedId.(bson.ObjectId).Hex()) - } - ids[i] = id - } - return ids, nil -} - // Compiles terms into something *mgo.Session can understand. -func (self *SourceCollection) compileConditions(term interface{}) interface{} { +func (self *Collection) compileConditions(term interface{}) interface{} { switch t := term.(type) { case []interface{}: @@ -196,14 +135,14 @@ func (self *SourceCollection) compileConditions(term interface{}) interface{} { for i, _ := range t { values = append(values, self.compileConditions(t[i])) } - condition := bson.M{"$or": values} + condition := bson.M{`$or`: values} return condition case db.And: values := []interface{}{} for i, _ := range t { values = append(values, self.compileConditions(t[i])) } - condition := bson.M{"$and": values} + condition := bson.M{`$and`: values} return condition case db.Cond: return compileStatement(t) @@ -212,7 +151,7 @@ func (self *SourceCollection) compileConditions(term interface{}) interface{} { } // Compiles terms into something that *mgo.Session can understand. -func (self *SourceCollection) compileQuery(terms ...interface{}) interface{} { +func (self *Collection) compileQuery(terms ...interface{}) interface{} { var query interface{} compiled := self.compileConditions(terms) @@ -242,134 +181,50 @@ func (self *SourceCollection) compileQuery(terms ...interface{}) interface{} { return query } -// Removes all the items that match the given conditions. -func (self *SourceCollection) Remove(terms ...interface{}) error { - - query := self.compileQuery(terms...) - - _, err := self.collection.RemoveAll(query) - - return err -} - -// Updates all the items that match the given conditions. -func (self *SourceCollection) Update(selector interface{}, update interface{}) error { - var err error - query := self.compileQuery(selector) - - _, err = self.collection.UpdateAll(query, bson.M{"$set": update}) - return err -} - -// Returns the number of items that match the given conditions. -func (self *SourceCollection) Count(terms ...interface{}) (int, error) { - q := self.buildQuery(terms...) - - count, err := q.Count() - - return count, err -} - -// Returns the first db.Item that matches the given conditions. -func (self *SourceCollection) Find(terms ...interface{}) (db.Item, error) { - terms = append(terms, db.Limit(1)) - - result, err := self.FindAll(terms...) +// Deletes all the rows within the collection. +func (self *Collection) Truncate() error { + err := self.collection.DropCollection() - if len(result) > 0 { - return result[0], nil + if err != nil { + return err } - return nil, err + return nil } -// Returns a *mgo.Query based on the given terms. -func (self *SourceCollection) buildQuery(terms ...interface{}) *mgo.Query { - - var delim = struct { - Limit int - Offset int - Fields *db.Fields - Sort *db.Sort - }{ - -1, - -1, - nil, - nil, - } - - // Conditions - query := self.compileQuery(terms...) - - for i, _ := range terms { - switch t := terms[i].(type) { - case db.Fields: - delim.Fields = &t - case db.Limit: - delim.Limit = int(t) - case db.Offset: - delim.Offset = int(t) - case db.Sort: - delim.Sort = &t - } - } +// Appends an item (map or struct) into the collection. +func (self *Collection) Append(item interface{}) (db.Id, error) { + var id db.Id - // Actually executing query, returning a pointer. - res := self.collection.Find(query) + // Dirty trick to return the Id with ease. + res, err := self.collection.Upsert(bson.M{"_id": nil}, toInternal(item)) - // Applying limits and offsets. - if delim.Offset > -1 { - res = res.Skip(delim.Offset) + if err != nil { + return nil, err } - if delim.Limit > -1 { - res = res.Limit(delim.Limit) + if res.UpsertedId != nil { + id = res.UpsertedId.(bson.ObjectId) } - // Delimiting fields. - if delim.Fields != nil { - sel := bson.M{} - for _, field := range *delim.Fields { - sel[field] = true - } - res = res.Select(sel) - } + return id, nil +} - // Sorting result. - if delim.Sort != nil { - for key, val := range *delim.Sort { - sval := to.String(val) - if sval == "-1" || sval == "DESC" { - res = res.Sort("-" + key) - } else if sval == "1" || sval == "ASC" { - res = res.Sort(key) - } else { - panic(fmt.Sprintf(`Unknown sort value "%s".`, sval)) - } - } +// Returns true if the collection exists. +func (self *Collection) Exists() bool { + query := self.parent.database.C(`system.namespaces`).Find(db.Item{`name`: fmt.Sprintf(`%s.%s`, self.parent.Name(), self.Name())}) + count, _ := query.Count() + if count > 0 { + return true } - - return res + 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.Id: - ids := make([]bson.ObjectId, len(t)) - for i, _ := range t { - ids[i] = bson.ObjectIdHex(string(t[i])) - } - return ids - case db.Id: - return bson.ObjectIdHex(string(t)) - case db.Item: - for k, _ := range t { - t[k] = toInternal(t[k]) - } case db.Cond: for k, _ := range t { t[k] = toInternal(t[k]) @@ -402,15 +257,3 @@ func toNative(val interface{}) interface{} { return val } - -// Returns all the items that match the given conditions. See Find(). -func (self *SourceCollection) FindAll(terms ...interface{}) ([]db.Item, error) { - var err error - results := []db.Item{} - q, err := self.Query(terms...) - if err != nil { - return nil, err - } - err = q.All(&results) - return results, err -} diff --git a/mongo/database.go b/mongo/database.go index fab2447e..e6cd8cb6 100644 --- a/mongo/database.go +++ b/mongo/database.go @@ -155,7 +155,7 @@ func (self *Source) Collections() ([]string, error) { func (self *Source) Collection(name string) (db.Collection, error) { var err error - col := &SourceCollection{} + col := &Collection{} col.parent = self col.collection = self.database.C(name) diff --git a/mongo/database_test.go b/mongo/database_test.go index e06435cf..cfaab25f 100644 --- a/mongo/database_test.go +++ b/mongo/database_test.go @@ -1,211 +1,240 @@ +/* + Copyright (c) 2012-2013 José Carlos Nieto, https://menteslibres.net/xiam + + 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. +*/ + +/* + Tests for the mongo wrapper. +*/ package mongo import ( - "fmt" - "github.com/kr/pretty" + "labix.org/v2/mgo" "labix.org/v2/mgo/bson" - "math/rand" - "upper.io/db" - "menteslibres.net/gosexy/dig" "menteslibres.net/gosexy/to" "reflect" + "strings" "testing" "time" + "upper.io/db" ) +// Wrapper. const wrapperName = "mongo" +// Wrapper settings. const host = "127.0.0.1" -const socket = "/tmp/mongodb-27017.sock" -const dbname = "gotest" -const username = "gouser" -const password = "gopass" +const dbname = "upperio_tests" -var settings = db.DataSource{ - Database: dbname, +// Global settings for tests. +var settings = db.Settings{ Host: host, - // https://bugs.launchpad.net/mgo/+bug/954436 - //Socket: socket, - - //User: username, - //Password: password, + Database: dbname, } -// Structure for testing conversions. +// Structure for testing conversions and datatypes. type testValuesStruct struct { - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - - Float32 float32 - Float64 float64 - - Bool bool - String string - - Date time.Time - Time time.Duration + Uint uint `bson:"_uint"` + Uint8 uint8 `bson:"_uint8"` + Uint16 uint16 `bson:"_uint16"` + Uint32 uint32 `bson:"_uint32"` + Uint64 uint64 `bson:"_uint64"` + + Int int `bson:"_int"` + Int8 int8 `bson:"_int8"` + Int16 int16 `bson:"_int16"` + Int32 int32 `bson:"_int32"` + Int64 int64 `bson:"_int64"` + + Float32 float32 `bson:"_float32"` + Float64 float64 `bson:"_float64"` + + Bool bool `bson:"_bool"` + String string `bson:"_string"` + + Date time.Time `bson:"_date"` + Time time.Duration `bson:"_time"` } -// Some test values. +// Declaring some values to insert, we expect the same values to be returned. var testValues = testValuesStruct{ 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1.337, 1.337, true, "Hello world!", - time.Date(2012, 7, 28, 1, 2, 3, 0, time.Local), + time.Unix(1234567890, 0), time.Second * time.Duration(7331), } -// Outputs some information to stdout, useful for development. +// Enabling outputting some information to stdout, useful for development. func TestEnableDebug(t *testing.T) { Debug = true } -/* -// Trying to open an empty datasource, must fail. +// Trying to open an empty datasource, it must succeed (mongo). func TestOpenFailed(t *testing.T) { - _, err := db.Open(wrapperName, db.DataSource{}) + _, err := db.Open(wrapperName, db.Settings{}) - if err == nil { - t.Errorf("Could not open database.") + if err != nil { + t.Errorf(err.Error()) } } -*/ -// Truncates all collections/tables, one by one. +// Truncates all collections. func TestTruncate(t *testing.T) { var err error + // Opening database. sess, err := db.Open(wrapperName, settings) if err != nil { t.Fatalf(err.Error()) } + // We should close the database when it's no longer in use. defer sess.Close() - collections := sess.Collections() + // Getting a list of all collections in this database. + collections, err := sess.Collections() - for _, name := range collections { - col, _ := sess.Collection(name) - col.Truncate() + if err != nil { + t.Fatalf(err.Error()) + } - total, err := col.Count() + for _, name := range collections { + // Pointing the collection. + col, err := sess.Collection(name) if err != nil { t.Fatalf(err.Error()) } - if total != 0 { - t.Errorf("Could not truncate.") + // The collection may ot may not exists. + exists := col.Exists() + + if exists == true { + // Truncating the structure, if exists. + err = col.Truncate() + + if err != nil { + t.Fatalf(err.Error()) + } } - } + } } -// Appends maps and structs. +// This test appends some data into the "artist" table. func TestAppend(t *testing.T) { + var err error + var id interface{} + + // Opening database. sess, err := db.Open(wrapperName, settings) if err != nil { t.Fatalf(err.Error()) } + // We should close the database when it's no longer in use. defer sess.Close() - _, err = sess.Collection("doesnotexists") + // Getting a pointer to the "artist" collection. + artist, err := sess.Collection("artist") - if err == nil { - t.Fatalf("Collection should not exists.") + if err != nil { + // We can use the collection even if it does not exists. + if err != db.ErrCollectionDoesNotExists { + t.Fatalf(err.Error()) + } } - people, _ := sess.Collection("people") + // Appending a map. + id, err = artist.Append(map[string]string{ + "name": "Ozzie", + }) - // To be inserted - names := []string{ - "Juan", - "José", - "Pedro", - "MarÃa", - "Roberto", - "Manuel", - "Miguel", + if id == nil { + t.Fatalf("Expecting an ID.") } - var total int - - // Append db.Item - people.Truncate() - - for _, name := range names { - people.Append(db.Item{"name": name}) + if _, ok := id.(bson.ObjectId); ok != true { + t.Fatalf("Expecting a bson.ObjectId.") } - total, _ = people.Count() - - if total != len(names) { - t.Fatalf("Could not append all items.") + if id.(bson.ObjectId).Valid() != true { + t.Fatalf("Expecting a valid bson.ObjectId.") } - // Append map[string]string - people.Truncate() + // Appending a struct. + id, err = artist.Append(struct { + Name string + }{ + "Flea", + }) - for _, name := range names { - people.Append(map[string]string{"name": name}) + if id == nil { + t.Fatalf("Expecting an ID.") } - total, _ = people.Count() - - if total != len(names) { - t.Fatalf("Could not append all items.") + if _, ok := id.(bson.ObjectId); ok != true { + t.Fatalf("Expecting a bson.ObjectId.") } - // Append map[string]interface{} - people.Truncate() - - for _, name := range names { - people.Append(map[string]interface{}{"name": name}) + if id.(bson.ObjectId).Valid() != true { + t.Fatalf("Expecting a valid bson.ObjectId.") } - total, _ = people.Count() + // Appending a struct (using tags to specify the field name). + id, err = artist.Append(struct { + ArtistName string `bson:"name"` + }{ + "Slash", + }) - if total != len(names) { - t.Fatalf("Could not append all items.") + if id == nil { + t.Fatalf("Expecting an ID.") } - // Append struct - people.Truncate() - - for _, name := range names { - people.Append(struct{ Name string }{name}) + if _, ok := id.(bson.ObjectId); ok != true { + t.Fatalf("Expecting a bson.ObjectId.") } - total, _ = people.Count() - - if total != len(names) { - t.Fatalf("Could not append all items.") + if id.(bson.ObjectId).Valid() != true { + t.Fatalf("Expecting a valid bson.ObjectId.") } } -/* -// Tries to find and fetch rows. -func TestFind(t *testing.T) { +// This test tries to use an empty filter and count how many elements were +// added into the artist collection. +func TestResultCount(t *testing.T) { var err error + var res db.Result + // Opening database. sess, err := db.Open(wrapperName, settings) if err != nil { @@ -214,627 +243,505 @@ func TestFind(t *testing.T) { defer sess.Close() - people := sess.ExistentCollection("people") + // We should close the database when it's no longer in use. + artist, _ := sess.Collection("artist") - // Testing Find() - result := people.Find(db.Cond{"name": "José"}) - - if result["name"] != "José" { - t.Fatalf("Could not find a recently appended item.") - } - - // Fetch into map slice. - dst := []map[string]string{} - - err = people.FetchAll(&dst, db.Cond{"name": "José"}) + res, err = artist.Filter() if err != nil { t.Fatalf(err.Error()) } - if len(dst) != 1 { - t.Fatalf("Could not find a recently appended item.") - } - - if dst[0]["name"] != "José" { - t.Fatalf("Could not find a recently appended item.") - } - - // Fetch into struct slice. - dst2 := []struct{ Name string }{} - - err = people.FetchAll(&dst2, db.Cond{"name": "José"}) + // Counting all the matching rows. + total, err := res.Count() if err != nil { t.Fatalf(err.Error()) } - if len(dst2) != 1 { - t.Fatalf("Could not find a recently appended item.") + if total == 0 { + t.Fatalf("Should not be empty, we've just added some rows!") } - if dst2[0].Name != "José" { - t.Fatalf("Could not find a recently appended item.") - } +} - // Fetch into map. - dst3 := map[string]interface{}{} +// This test uses and result and tries to fetch items one by one. +func TestResultFetch(t *testing.T) { - err = people.Fetch(&dst3, db.Cond{"name": "José"}) + var err error + var res db.Result + + // Opening database. + sess, err := db.Open(wrapperName, settings) if err != nil { t.Fatalf(err.Error()) } - if dst3["name"] != "José" { - t.Fatalf("Could not find a recently appended item.") - } - - // Fetch into struct. - dst4 := struct{ Name string }{} + // We should close the database when it's no longer in use. + defer sess.Close() - err = people.Fetch(&dst4, db.Cond{"name": "José"}) + artist, err := sess.Collection("artist") if err != nil { t.Fatalf(err.Error()) } - if dst4.Name != "José" { - t.Fatalf("Could not find a recently appended item.") - } - -} -*/ - -// Tries to find and fetch rows. -func TestFind(t *testing.T) { - - var err error - var res db.Result - - sess, err := db.Open(wrapperName, settings) + // Testing map + res, err = artist.Filter() if err != nil { t.Fatalf(err.Error()) } - defer sess.Close() + row_m := map[string]interface{}{} - people, _ := sess.Collection("people") + for { + err = res.Next(&row_m) - // Testing Find() - item, _ := people.Find(db.Cond{"name": "José"}) + if err == db.ErrNoMoreRows { + // No more row_ms left. + break + } + + if err == nil { + if row_m["_id"] == nil { + t.Fatalf("Expecting an ID.") + } + if _, ok := row_m["_id"].(bson.ObjectId); ok != true { + t.Fatalf("Expecting a bson.ObjectId.") + } - if item["name"] != "José" { - t.Fatalf("Could not find a recently appended item.") + if row_m["_id"].(bson.ObjectId).Valid() != true { + t.Fatalf("Expecting a valid bson.ObjectId.") + } + if to.String(row_m["name"]) == "" { + t.Fatalf("Expecting a name.") + } + } else { + t.Fatalf(err.Error()) + } } - // Fetch into map slice. - dst := []map[string]string{} + res.Close() + + // Testing struct + row_s := struct { + Id bson.ObjectId `bson:"_id"` + Name string `bson:"name"` + }{} - res, err = people.Query(db.Cond{"name": "José"}) + res, err = artist.Filter() if err != nil { t.Fatalf(err.Error()) } - err = res.All(&dst) + for { + err = res.Next(&row_s) - if err != nil { - t.Fatalf(err.Error()) - } + if err == db.ErrNoMoreRows { + // No more row_s' left. + break + } - if len(dst) != 1 { - t.Fatalf("Could not find a recently appended item.") + if err == nil { + if row_s.Id.Valid() == false { + t.Fatalf("Expecting a not null ID.") + } + if row_s.Name == "" { + t.Fatalf("Expecting a name.") + } + } else { + t.Fatalf(err.Error()) + } } - if dst[0]["name"] != "José" { - t.Fatalf("Could not find a recently appended item.") - } + res.Close() - // Fetch into struct slice. - dst2 := []struct{ Name string }{} + // Testing tagged struct + row_t := struct { + Value1 bson.ObjectId `bson:"_id"` + Value2 string `bson:"name"` + }{} - res, err = people.Query(db.Cond{"name": "José"}) + res, err = artist.Filter() if err != nil { t.Fatalf(err.Error()) } - err = res.All(&dst2) - - if err != nil { - t.Fatalf(err.Error()) - } + for { + err = res.Next(&row_t) - if len(dst2) != 1 { - t.Fatalf("Could not find a recently appended item.") - } + if err == db.ErrNoMoreRows { + // No more row_t's left. + break + } - if dst2[0].Name != "José" { - t.Fatalf("Could not find a recently appended item.") + if err == nil { + if row_t.Value1.Valid() == false { + t.Fatalf("Expecting a not null ID.") + } + if row_t.Value2 == "" { + t.Fatalf("Expecting a name.") + } + } else { + t.Fatalf(err.Error()) + } } - // Fetch into map. - dst3 := map[string]interface{}{} + res.Close() - res, err = people.Query(db.Cond{"name": "José"}) + // Testing Result.All() with a slice of maps. + res, err = artist.Filter() if err != nil { t.Fatalf(err.Error()) } - err = res.One(&dst3) + all_rows_m := []map[string]interface{}{} + err = res.All(&all_rows_m) if err != nil { t.Fatalf(err.Error()) } - // Fetch into struct. - dst4 := struct{ Name string }{} + for _, single_row_m := range all_rows_m { + if single_row_m["_id"] == nil { + t.Fatalf("Expecting a not null ID.") + } + } - res, err = people.Query(db.Cond{"name": "José"}) + // Testing Result.All() with a slice of structs. + res, err = artist.Filter() if err != nil { t.Fatalf(err.Error()) } - err = res.One(&dst4) + all_rows_s := []struct { + Id bson.ObjectId `bson:"_id"` + Name string + }{} + err = res.All(&all_rows_s) if err != nil { t.Fatalf(err.Error()) } - if dst4.Name != "José" { - t.Fatalf("Could not find a recently appended item.") + for _, single_row_s := range all_rows_s { + if single_row_s.Id.Valid() == false { + t.Fatalf("Expecting a not null ID.") + } } - // Makes a query and stores the result - res, err = people.Query(nil) + // Testing Result.All() with a slice of tagged structs. + res, err = artist.Filter() if err != nil { t.Fatalf(err.Error()) } - dst5 := struct{ Name string }{} - found := false + all_rows_t := []struct { + Value1 bson.ObjectId `bson:"_id"` + Value2 string `bson:"name"` + }{} + err = res.All(&all_rows_t) - for { - err = res.Next(&dst5) - if err != nil { - break - } - if dst5.Name == "José" { - found = true - } + if err != nil { + t.Fatalf(err.Error()) } - res.Close() - - if found == false { - t.Fatalf("José was not found.") + for _, single_row_t := range all_rows_t { + if single_row_t.Value1.Valid() == false { + t.Fatalf("Expecting a not null ID.") + } } - } -// Tests limit and offset. -func TestLimitOffset(t *testing.T) { - +// This test tries to update some previously added rows. +func TestUpdate(t *testing.T) { var err error + // Opening database. sess, err := db.Open(wrapperName, settings) if err != nil { t.Fatalf(err.Error()) } + // We should close the database when it's no longer in use. defer sess.Close() - people, _ := sess.Collection("people") - - items, _ := people.FindAll(db.Limit(2), db.Offset(1)) + // Getting a pointer to the "artist" collection. + artist, err := sess.Collection("artist") - if len(items) != 2 { - t.Fatalf("Test failed") + if err != nil { + t.Fatalf(err.Error()) } -} + // Value + value := struct { + Id bson.ObjectId `bson:"_id"` + Name string + }{} -// Tries to delete rows. -func TestDelete(t *testing.T) { - sess, err := db.Open(wrapperName, settings) + // Getting the first artist. + res, err := artist.Filter(db.Cond{"_id $ne": nil}, db.Limit(1)) if err != nil { t.Fatalf(err.Error()) } - defer sess.Close() - - people, _ := sess.Collection("people") + err = res.One(&value) - people.Remove(db.Cond{"name": "Juan"}) - - result, _ := people.Find(db.Cond{"name": "Juan"}) - - if len(result) > 0 { - t.Fatalf("Could not remove a recently appended item.") + if err != nil { + t.Fatalf(err.Error()) } -} + // Updating with a map + row_m := map[string]interface{}{ + "name": strings.ToUpper(value.Name), + } -// Tries to update rows. -func TestUpdate(t *testing.T) { - var found int - - sess, err := db.Open(wrapperName, settings) + err = res.Update(row_m) if err != nil { t.Fatalf(err.Error()) } - defer sess.Close() - - people := sess.ExistentCollection("people") - - // Update with map. - people.Update(db.Cond{"name": "José"}, db.Set{"name": "Joseph"}) + err = res.One(&value) - found, _ = people.Count(db.Cond{"name": "Joseph"}) + if err != nil { + t.Fatalf(err.Error()) + } - if found != 1 { - t.Fatalf("Could not update a recently appended item.") + if value.Name != row_m["name"] { + t.Fatalf("Expecting a modification.") } - // Update with struct. - people.Update(db.Cond{"name": "Joseph"}, struct{ Name string }{"José"}) + // Updating with a struct + row_s := struct { + Name string + }{strings.ToLower(value.Name)} - found, _ = people.Count(db.Cond{"name": "José"}) + err = res.Update(row_s) - if found != 1 { - t.Fatalf("Could not update a recently appended item.") + if err != nil { + t.Fatalf(err.Error()) } -} - -// Tries to add test data and relations. -func TestPopulate(t *testing.T) { - sess, err := db.Open(wrapperName, settings) + err = res.One(&value) if err != nil { - t.Errorf(err.Error()) + t.Fatalf(err.Error()) } - defer sess.Close() + if value.Name != row_s.Name { + t.Fatalf("Expecting a modification.") + } - people, _ := sess.Collection("people") - places, _ := sess.Collection("places") - children, _ := sess.Collection("children") - visits, _ := sess.Collection("visits") + // Updating with a tagged struct + row_t := struct { + Value1 string `bson:"name"` + }{strings.Replace(value.Name, "z", "Z", -1)} - values := []string{"Alaska", "Nebraska", "Alaska", "Acapulco", "Rome", "Singapore", "Alabama", "Cancún"} + err = res.Update(row_t) - for i, value := range values { - places.Append(db.Item{ - "code_id": i, - "name": value, - }) + if err != nil { + t.Fatalf(err.Error()) } - results, _ := people.FindAll( - db.Fields{"_id", "name"}, - db.Sort{"name": "ASC", "_id": -1}, - ) - - for _, person := range results { + err = res.One(&value) - // Has 5 children. - - for j := 0; j < 5; j++ { - children.Append(db.Item{ - "name": fmt.Sprintf("%s's child %d", person["name"], j+1), - "parent_id": person["_id"], - }) - } + if err != nil { + t.Fatalf(err.Error()) + } - // Lives in - people.Update( - db.Cond{"_id": person["_id"]}, - db.Set{"place_code_id": int(rand.Float32() * float32(len(results)))}, - ) - - // Has visited - for j := 0; j < 3; j++ { - place, _ := places.Find(db.Cond{ - "code_id": int(rand.Float32() * float32(len(results))), - }) - visits.Append(db.Item{ - "place_id": place["_id"], - "person_id": person["_id"], - }) - } + if value.Name != row_t.Value1 { + t.Fatalf("Expecting a modification.") } } -// Tests relations between collections. -func TestRelation(t *testing.T) { +// This test tries to remove some previously added rows. +func TestRemove(t *testing.T) { + + var err error + + // Opening database. sess, err := db.Open(wrapperName, settings) if err != nil { - t.Errorf(err.Error()) + t.Fatalf(err.Error()) } + // We should close the database when it's no longer in use. defer sess.Close() - people, _ := sess.Collection("people") + // Getting a pointer to the "artist" collection. + artist, err := sess.Collection("artist") - results, _ := people.FindAll( - db.Relate{ - "lives_in": db.On{ - sess.ExistentCollection("places"), - db.Cond{"code_id": "{place_code_id}"}, - }, - }, - db.RelateAll{ - "has_children": db.On{ - sess.ExistentCollection("children"), - db.Cond{"parent_id": "{_id}"}, - }, - "has_visited": db.On{ - sess.ExistentCollection("visits"), - db.Cond{"person_id": "{_id}"}, - db.Relate{ - "place": db.On{ - sess.ExistentCollection("places"), - db.Cond{"_id": "{place_id}"}, - }, - }, - }, - }, - ) - - fmt.Printf("relations (1) %# v\n", pretty.Formatter(results)) - - var testv string - - testv = dig.String(&results, 0, "lives_in", "name") - - if testv == "" { - t.Fatalf("Test failed, expected some value.") + if err != nil { + t.Fatalf(err.Error()) } - testv = dig.String(&results, 1, "has_children", 2, "name") + // Getting the first artist. + res, err := artist.Filter(db.Cond{"_id $ne": nil}, db.Limit(1)) - if testv == "" { - t.Fatalf("Test failed, expected some value.") + if err != nil { + t.Fatalf(err.Error()) } -} -// Tests relations between collections using structs. -func TestRelationStruct(t *testing.T) { - var err error - var res db.Result + var first struct { + Id bson.ObjectId `bson:"_id"` + } - sess, err := db.Open(wrapperName, settings) + err = res.One(&first) if err != nil { - t.Errorf(err.Error()) + t.Fatalf(err.Error()) } - defer sess.Close() - - people := sess.ExistentCollection("people") - - results := []struct { - Id bson.ObjectId `_id` - Name string - PlaceCodeId int `place_code_id` - LivesIn struct { - Name string - } - HasChildren []struct { - Name string - } - HasVisited []struct { - PlaceId bson.ObjectId `place_id` - Place struct { - Name string - } - } - }{} - - res, err = people.Query( - db.Relate{ - "LivesIn": db.On{ - sess.ExistentCollection("places"), - db.Cond{"code_id": "{PlaceCodeId}"}, - }, - }, - db.RelateAll{ - "HasChildren": db.On{ - sess.ExistentCollection("children"), - db.Cond{"parent_id": "{Id}"}, - }, - "HasVisited": db.On{ - sess.ExistentCollection("visits"), - db.Cond{"person_id": "{Id}"}, - db.Relate{ - "Place": db.On{ - sess.ExistentCollection("places"), - db.Cond{"_id": "{PlaceId}"}, - }, - }, - }, - }, - ) + res, err = artist.Filter(db.Cond{"_id": first.Id}) if err != nil { t.Fatalf(err.Error()) } - err = res.All(&results) + // Trying to remove the row. + err = res.Remove() if err != nil { t.Fatalf(err.Error()) } - - fmt.Printf("relations (2) %# v\n", pretty.Formatter(results)) } -// Tests datatype conversions. +// This test tries to add many different datatypes to a single row in a +// collection, then it tries to get the stored datatypes and check if the +// stored and the original values match. func TestDataTypes(t *testing.T) { var res db.Result - var items []db.Item + // Opening database. sess, err := db.Open(wrapperName, settings) if err != nil { t.Fatalf(err.Error()) } + // We should close the database when it's no longer in use. defer sess.Close() - dataTypes, _ := sess.Collection("data_types") - + // Getting a pointer to the "data_types" collection. + dataTypes, err := sess.Collection("data_types") dataTypes.Truncate() - ids, err := dataTypes.Append(testValues) + // Appending our test subject. + id, err := dataTypes.Append(testValues) if err != nil { t.Fatalf(err.Error()) } - found, err := dataTypes.Count(db.Cond{"_id": db.Id(ids[0])}) + // Trying to get the same subject we added. + res, err = dataTypes.Filter(db.Cond{"_id": id}) if err != nil { t.Fatalf(err.Error()) } - if found == 0 { - t.Errorf("Expecting an item.") + exists, err := res.Count() + + if err != nil { + t.Fatalf(err.Error()) } - // Getting and reinserting (a db.Item). - item, _ := dataTypes.Find() + if exists == 0 { + t.Errorf("Expecting an item.") + } - _, err = dataTypes.Append(item) + // Trying to dump the subject into an empty structure of the same type. + var item testValuesStruct + res.One(&item) - if err == nil { - t.Fatalf("Expecting duplicated-key error.") + // The original value and the test subject must match. + if reflect.DeepEqual(item, testValues) == false { + t.Errorf("Struct is different.") } +} - delete(item, "_id") +// We are going to benchmark the engine, so this is no longed needed. +func TestDisableDebug(t *testing.T) { + Debug = false +} - _, err = dataTypes.Append(item) +// Benchmarking raw mgo queries. +func BenchmarkAppendRaw(b *testing.B) { + sess, err := db.Open(wrapperName, settings) if err != nil { - t.Fatalf(err.Error()) + b.Fatalf(err.Error()) } - // Testing date ranges - items, err = dataTypes.FindAll(db.Cond{ - "date": time.Now(), - }) + defer sess.Close() - if err != nil { - t.Fatalf(err.Error()) - } + artist, err := sess.Collection("artist") + artist.Truncate() - if len(items) > 0 { - t.Fatalf("Expecting no results.") - } + driver := sess.Driver().(*mgo.Session) - items, err = dataTypes.FindAll(db.Cond{ - "date <=": time.Now(), - }) + mgodb := driver.DB(dbname) + col := mgodb.C("artist") - if err != nil { - t.Fatalf(err.Error()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := col.Insert(map[string]string{"name": "Hayao Miyazaki"}) + if err != nil { + b.Fatalf(err.Error()) + } } +} - if len(items) != 2 { - t.Fatalf("Expecting some results.") +func BenchmarkAppendDbItem(b *testing.B) { + sess, err := db.Open(wrapperName, settings) + + if err != nil { + b.Fatalf(err.Error()) } - // Testing struct - sresults := []testValuesStruct{} + defer sess.Close() - res, err = dataTypes.Query() + artist, err := sess.Collection("artist") + artist.Truncate() - if err != nil { - t.Fatalf(err.Error()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err = artist.Append(map[string]string{"name": "Leonardo DaVinci"}) + if err != nil { + b.Fatalf(err.Error()) + } } +} - err = res.All(&sresults) +func BenchmarkAppendStruct(b *testing.B) { + sess, err := db.Open(wrapperName, settings) if err != nil { - t.Fatalf(err.Error()) + b.Fatalf(err.Error()) } - // Testing struct equality - for _, item := range sresults { - if reflect.DeepEqual(item, testValues) == false { - t.Errorf("Struct is different.") - } - } + defer sess.Close() - // Testing maps - results, _ := dataTypes.FindAll() - - for _, item := range results { - - for key, _ := range item { - - switch key { - - // Signed integers. - case - "_int", - "_int8", - "_int16", - "_int32", - "_int64": - if to.Int64(item[key]) != testValues.Int64 { - t.Fatalf("Wrong datatype %v.", key) - } - - // Unsigned integers. - case - "_uint", - "_uint8", - "_uint16", - "_uint32", - "_uint64": - if to.Uint64(item[key]) != testValues.Uint64 { - t.Fatalf("Wrong datatype %v.", key) - } - - // Floating point. - case "_float32": - case "_float64": - if to.Float64(item[key]) != testValues.Float64 { - t.Fatalf("Wrong datatype %v.", key) - } - - // Boolean - case "_bool": - if to.Bool(item[key]) != testValues.Bool { - t.Fatalf("Wrong datatype %v.", key) - } - - // String - case "_string": - if to.String(item[key]) != testValues.String { - t.Fatalf("Wrong datatype %v.", key) - } - - // Date - case "_date": - if to.Time(item[key]).Equal(testValues.Date) == false { - t.Fatalf("Wrong datatype %v.", key) - } - } + artist, err := sess.Collection("artist") + artist.Truncate() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err = artist.Append(struct{ Name string }{"John Lennon"}) + if err != nil { + b.Fatalf(err.Error()) } } - } diff --git a/mongo/result.go b/mongo/result.go index 8bbed649..c69acfad 100644 --- a/mongo/result.go +++ b/mongo/result.go @@ -24,75 +24,168 @@ package mongo import ( + "fmt" "labix.org/v2/mgo" + "labix.org/v2/mgo/bson" + "menteslibres.net/gosexy/to" "upper.io/db" - "upper.io/db/util" + //"upper.io/db/util" + "errors" ) type Result struct { - query *mgo.Query - collection *util.C - iter *mgo.Iter - relations []db.Relation + c *Collection + queryChunks *chunks + //collection *util.C + iter *mgo.Iter } +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() + if err != nil { + return err + } + self.iter = q.Iter() + } + return nil +} + +// Dumps all results into a pointer to an slice of structs or maps. func (self *Result) All(dst interface{}) error { + var err error - err = self.query.All(dst) + err = self.setCursor() if err != nil { return err } - // Fetching relations - err = self.collection.FetchRelations(dst, self.relations, toInternal) + err = self.iter.All(dst) if err != nil { return err } - dst = toNative(dst) + self.Close() return nil } -func (self *Result) Next(dst interface{}) error { +// Fetches only one result from the resultset. +func (self *Result) One(dst interface{}) error { + var err error + err = self.Next(dst) - if self.iter.Next(dst) == false { - return db.ErrNoMoreRows + if err != nil { + return err } - if self.iter.Err() != nil { - return self.iter.Err() + self.Close() + + return nil +} + +// Fetches the next result from the resultset. +func (self *Result) Next(dst interface{}) error { + err := self.setCursor() + + if err != nil { + return err } - self.collection.FetchRelation(dst, self.relations, toInternal) + success := self.iter.Next(dst) - dst = toNative(dst) + if success == false { + return db.ErrNoMoreRows + } return nil } -func (self *Result) One(dst interface{}) error { +// Removes the matching items from the collection. +func (self *Result) Remove() error { var err error - - err = self.query.One(dst) + _, err = self.c.collection.RemoveAll(self.queryChunks.Conditions) if err != nil { return err } + return nil +} - err = self.collection.FetchRelation(dst, self.relations, toInternal) +// Closes the result set. +func (self *Result) Close() error { + var err error + if self.iter != nil { + err = self.iter.Close() + self.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}) if err != nil { return err } + return nil +} - dst = toNative(dst) +func (self *Result) query() (*mgo.Query, error) { + var err error - return err + q := self.c.collection.Find(self.queryChunks.Conditions) + + if self.queryChunks.Offset > 0 { + q = q.Skip(self.queryChunks.Offset) + } + + if self.queryChunks.Limit > 0 { + q = q.Limit(self.queryChunks.Limit) + } + + if self.queryChunks.Fields != nil { + sel := bson.M{} + for _, field := range self.queryChunks.Fields { + if field == `*` { + break + } + sel[field] = true + } + q = q.Select(sel) + } + + if self.queryChunks.Sort != nil { + for key, val := range *self.queryChunks.Sort { + sval := to.String(val) + if sval == "-1" || sval == "DESC" { + q = q.Sort("-" + key) + } else if sval == "1" || sval == "ASC" { + q = q.Sort(key) + } else { + return nil, fmt.Errorf(errUnknownSortValue.Error(), sval) + } + } + } + + return q, err } -func (self *Result) Close() error { - return nil +// Counts matching elements. +func (self *Result) Count() (uint64, error) { + q, err := self.query() + if err != nil { + return 0, err + } + total, err := q.Count() + return uint64(total), err } -- GitLab