diff --git a/db.go b/db.go index 03b0607303fca2f5c70b305541ae06e6adb32057..104e258b85bb9a6441d7bc2dd044db4799bb1762 100644 --- a/db.go +++ b/db.go @@ -189,6 +189,8 @@ type Database interface { Drop() error Setup(DataSource) error + + Name() string } // Collection methods. @@ -206,6 +208,8 @@ type Collection interface { Remove(...interface{}) error Truncate() error + + Name() string } // Specifies which fields to return in a query. diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go index 73979d1e2a92b489356d435ecf07f6a352732c09..50b2e69053391f294fc7d3f1a57824895c63c79d 100644 --- a/sqlite/sqlite.go +++ b/sqlite/sqlite.go @@ -29,7 +29,8 @@ import ( "github.com/gosexy/db" "github.com/gosexy/sugar" "github.com/gosexy/to" - _ "github.com/xiam/gosqlite3" + //_ "github.com/xiam/gosqlite3" + _ "bitbucket.org/minux/go.sqlite3" "reflect" "regexp" "strconv" @@ -42,6 +43,10 @@ var Debug = false const dateFormat = "2006-01-02 15:04:05" const timeFormat = "%d:%02d:%02d" +func init() { + db.Register("sqlite", &SqliteDataSource{}) +} + type slQuery struct { Query []string SqlArgs []string @@ -93,9 +98,18 @@ func slValues(values []string) db.SqlValues { type SqliteDataSource struct { config db.DataSource session *sql.DB + name string collections map[string]db.Collection } +func (self *SqliteDataSource) Name() string { + return self.config.Database +} + +func (self *SqliteTable) Name() string { + return self.name +} + func (t *SqliteTable) slFetchAll(rows sql.Rows) []db.Item { items := []db.Item{} @@ -195,6 +209,13 @@ type SqliteTable struct { } // Configures and returns a SQLite database session. +func (m *SqliteDataSource) Setup(config db.DataSource) error { + m.config = config + m.collections = make(map[string]db.Collection) + return m.Open() +} + +// Deprecated: Configures and returns a SQLite database session. func SqliteSession(config db.DataSource) db.Database { m := &SqliteDataSource{} m.config = config @@ -228,6 +249,7 @@ func (sl *SqliteDataSource) Open() error { // Closes a previously opened SQLite database session. func (sl *SqliteDataSource) Close() error { + fmt.Printf("close: %v\n", sl.session) if sl.session != nil { return sl.session.Close() } @@ -374,9 +396,11 @@ func (t *SqliteTable) Truncate() error { _, err := t.parent.slExec( "Exec", - fmt.Sprintf("DELETE FROM %s", slTable(t.name)), + fmt.Sprintf("DELETE FROM %s", t.Name()), ) + fmt.Printf("E: %v\n", err) + return err } @@ -505,7 +529,7 @@ func (t *SqliteTable) FindAll(terms ...interface{}) []db.Item { } if rcollection == nil { - rcollection = t.parent.Collection(rname) + rcollection = t.parent.ExistentCollection(rname) } relations = append(relations, sugar.Tuple{"all": false, "name": rname, "collection": rcollection, "terms": rterms}) @@ -526,7 +550,7 @@ func (t *SqliteTable) FindAll(terms ...interface{}) []db.Item { } if rcollection == nil { - rcollection = t.parent.Collection(rname) + rcollection = t.parent.ExistentCollection(rname) } relations = append(relations, sugar.Tuple{"all": true, "name": rname, "collection": rcollection, "terms": rterms}) @@ -718,11 +742,40 @@ func (t *SqliteTable) Append(items ...interface{}) ([]db.Id, error) { return ids, nil } +// Returns true if the collection exists. +func (self *SqliteTable) Exists() bool { + result, err := self.parent.slExec( + "Query", + fmt.Sprintf(` + SELECT name + FROM sqlite_master + WHERE type = 'table' AND name = '%s' + `, + self.Name(), + ), + ) + if err != nil { + panic(err.Error()) + } + if result.Next() == true { + return true + } + return false +} + +func (self *SqliteDataSource) ExistentCollection(name string) db.Collection { + col, err := self.Collection(name) + if err != nil { + panic(err) + } + return col +} + // Returns a SQLite table structure by name. -func (sl *SqliteDataSource) Collection(name string) db.Collection { +func (sl *SqliteDataSource) Collection(name string) (db.Collection, error) { if collection, ok := sl.collections[name]; ok == true { - return collection + return collection, nil } t := &SqliteTable{} @@ -730,12 +783,17 @@ func (sl *SqliteDataSource) Collection(name string) db.Collection { t.parent = sl t.name = name + // Table exists? + if t.Exists() == false { + return t, fmt.Errorf("Table %s does not exists.", name) + } + // Fetching table datatypes and mapping to internal gotypes. rows, err := t.parent.session.Query(fmt.Sprintf("PRAGMA TABLE_INFO('%s')", t.name)) if err != nil { - panic(err) + return t, err } columns := t.slFetchAll(*rows) @@ -786,5 +844,5 @@ func (sl *SqliteDataSource) Collection(name string) db.Collection { sl.collections[name] = t - return t + return t, nil } diff --git a/sqlite/sqlite_test.go b/sqlite/sqlite_test.go index 96d91e9a0906f458b89e82420ab67a9d1715a68c..408b129e183256486c296acee14b964b3df88db0 100644 --- a/sqlite/sqlite_test.go +++ b/sqlite/sqlite_test.go @@ -3,16 +3,15 @@ package sqlite import ( "fmt" "github.com/gosexy/db" - "github.com/gosexy/sugar" "github.com/kr/pretty" "math/rand" "testing" "time" ) -const sqDatabase = "./dumps/gotest.sqlite3.db" +const dbpath = "./dumps/gotest.sqlite3.db" -func getTestData() db.Item { +func testItem() db.Item { _time, _ := time.ParseDuration("17h20m") @@ -55,76 +54,82 @@ func getTestData() db.Item { } func TestEnableDebug(t *testing.T) { - Debug = true + // Debug = true } -func TestSqTruncate(t *testing.T) { +func TestTruncate(t *testing.T) { - sess := SqliteSession(db.DataSource{Database: sqDatabase}) - - err := sess.Open() - defer sess.Close() + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) if err != nil { panic(err) } + defer sess.Close() + collections := sess.Collections() for _, name := range collections { - col := sess.Collection(name) + col := sess.ExistentCollection(name) col.Truncate() + total, _ := col.Count() + if total != 0 { - t.Errorf("Could not truncate '%s'.", name) + t.Errorf("Could not truncate %s.", name) } } } -func TestSqAppend(t *testing.T) { +func TestAppend(t *testing.T) { - sess := SqliteSession(db.DataSource{Database: sqDatabase}) - - err := sess.Open() - defer sess.Close() + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) if err != nil { panic(err) } - col := sess.Collection("people") + defer sess.Close() + + _, err = sess.Collection("doesnotexists") + + if err == nil { + t.Errorf("Collection should not exists.") + return + } + + people := sess.ExistentCollection("people") - col.Truncate() + people.Truncate() names := []string{"Juan", "José", "Pedro", "MarÃa", "Roberto", "Manuel", "Miguel"} for i := 0; i < len(names); i++ { - col.Append(db.Item{"name": names[i]}) + people.Append(db.Item{"name": names[i]}) } - total, _ := col.Count() + total, _ := people.Count() if total != len(names) { - panic(fmt.Errorf("Could not append all items")) + t.Error("Could not append all items.") } } -func TestSqFind(t *testing.T) { - - sess := SqliteSession(db.DataSource{Database: sqDatabase}) +func TestFind(t *testing.T) { - err := sess.Open() - defer sess.Close() + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) if err != nil { panic(err) } - col := sess.Collection("people") + defer sess.Close() - result := col.Find(db.Cond{"name": "José"}) + people, _ := sess.Collection("people") + + result := people.Find(db.Cond{"name": "José"}) if result["name"] != "José" { t.Error("Could not find a recently appended item.") @@ -132,137 +137,138 @@ func TestSqFind(t *testing.T) { } -func TestSqDelete(t *testing.T) { - sess := SqliteSession(db.DataSource{Database: sqDatabase}) - - err := sess.Open() - defer sess.Close() +func TestDelete(t *testing.T) { + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) if err != nil { panic(err) } - col := sess.Collection("people") + defer sess.Close() + + people, _ := sess.Collection("people") - col.Remove(db.Cond{"name": "Juan"}) + people.Remove(db.Cond{"name": "Juan"}) - result := col.Find(db.Cond{"name": "Juan"}) + result := people.Find(db.Cond{"name": "Juan"}) if len(result) > 0 { t.Error("Could not remove a recently appended item.") } -} -func TestSqUpdate(t *testing.T) { - sess := SqliteSession(db.DataSource{Database: sqDatabase}) +} - err := sess.Open() - defer sess.Close() +func TestUpdate(t *testing.T) { + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) if err != nil { panic(err) } - col := sess.Collection("people") + defer sess.Close() + + sess.Use("test") + + people, _ := sess.Collection("people") - col.Update(db.Cond{"name": "José"}, db.Set{"name": "Joseph"}) + people.Update(db.Cond{"name": "José"}, db.Set{"name": "Joseph"}) - result := col.Find(db.Cond{"name": "Joseph"}) + result := people.Find(db.Cond{"name": "Joseph"}) if len(result) == 0 { t.Error("Could not update a recently appended item.") } } -func TestSqPopulate(t *testing.T) { - var i int +func TestPopulate(t *testing.T) { - sess := SqliteSession(db.DataSource{Database: sqDatabase}) - - err := sess.Open() - defer sess.Close() + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) if err != nil { panic(err) } - sess.Use("test") + defer sess.Close() - places := []string{"Alaska", "Nebraska", "Alaska", "Acapulco", "Rome", "Singapore", "Alabama", "Cancún"} + people, _ := sess.Collection("people") + places, _ := sess.Collection("places") + children, _ := sess.Collection("children") + visits, _ := sess.Collection("visits") - for i = 0; i < len(places); i++ { - sess.Collection("places").Append(db.Item{ + values := []string{"Alaska", "Nebraska", "Alaska", "Acapulco", "Rome", "Singapore", "Alabama", "Cancún"} + + for i, value := range values { + places.Append(db.Item{ "code_id": i, - "name": places[i], + "name": value, }) } - people := sess.Collection("people").FindAll( + results := people.FindAll( db.Fields{"id", "name"}, db.Sort{"name": "ASC", "id": -1}, ) - for i = 0; i < len(people); i++ { - person := people[i] + for _, person := range results { // Has 5 children. + for j := 0; j < 5; j++ { - sess.Collection("children").Append(db.Item{ + children.Append(db.Item{ "name": fmt.Sprintf("%s's child %d", person["name"], j+1), - "parent_id": person["id"], + "parent_id": person["_id"], }) } // Lives in - sess.Collection("people").Update( - db.Cond{"id": person["id"]}, - db.Set{"place_code_id": int(rand.Float32() * float32(len(places)))}, + people.Update( + db.Cond{"_id": person["_id"]}, + db.Set{"place_code_id": int(rand.Float32() * float32(len(results)))}, ) // Has visited for k := 0; k < 3; k++ { - place := sess.Collection("places").Find(db.Cond{ - "code_id": int(rand.Float32() * float32(len(places))), + place := places.Find(db.Cond{ + "code_id": int(rand.Float32() * float32(len(results))), }) - sess.Collection("visits").Append(db.Item{ - "place_id": place["id"], - "person_id": person["id"], + visits.Append(db.Item{ + "place_id": place["_id"], + "person_id": person["_id"], }) } } } -func TestSqRelation(t *testing.T) { - sess := SqliteSession(db.DataSource{Database: sqDatabase}) - - err := sess.Open() - defer sess.Close() +func TestRelation(t *testing.T) { + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) if err != nil { panic(err) } - col := sess.Collection("people") + defer sess.Close() + + people, _ := sess.Collection("people") - result := col.FindAll( + results := people.FindAll( db.Relate{ "lives_in": db.On{ - sess.Collection("places"), + sess.ExistentCollection("places"), db.Cond{"code_id": "{place_code_id}"}, }, }, db.RelateAll{ "has_children": db.On{ - sess.Collection("children"), + sess.ExistentCollection("children"), db.Cond{"parent_id": "{id}"}, }, "has_visited": db.On{ - sess.Collection("visits"), + sess.ExistentCollection("visits"), db.Cond{"person_id": "{id}"}, db.Relate{ "place": db.On{ - sess.Collection("places"), + sess.ExistentCollection("places"), db.Cond{"id": "{place_id}"}, }, }, @@ -270,41 +276,43 @@ func TestSqRelation(t *testing.T) { }, ) - fmt.Printf("%# v\n", pretty.Formatter(result)) + fmt.Printf("%# v\n", pretty.Formatter(results)) } func TestDataTypes(t *testing.T) { - sess := SqliteSession(db.DataSource{Database: sqDatabase}) + sess, err := db.Open("sqlite", db.DataSource{Database: dbpath}) - err := sess.Open() - - if err == nil { - defer sess.Close() + if err != nil { + t.Errorf(err.Error()) + return } - col := sess.Collection("data_types") + defer sess.Close() - col.Truncate() + dataTypes, _ := sess.Collection("data_types") - data := getTestData() + dataTypes.Truncate() - ids, err := col.Append(data) + testData := testItem() + + ids, err := dataTypes.Append(testData) if err != nil { - t.Errorf("Could not append test data.") + t.Errorf("Could not append test data: %s.", err.Error()) } - found, _ := col.Count(db.Cond{"id": db.Id(ids[0])}) + found, _ := dataTypes.Count(db.Cond{"id": db.Id(ids[0])}) if found == 0 { t.Errorf("Cannot find recently inserted item (by ID).") } // Getting and reinserting. - item := col.Find() - _, err = col.Append(item) + item := dataTypes.Find() + + _, err = dataTypes.Append(item) if err == nil { t.Errorf("Expecting duplicated-key error.") @@ -312,18 +320,17 @@ func TestDataTypes(t *testing.T) { delete(item, "id") - _, err = col.Append(item) + _, err = dataTypes.Append(item) if err != nil { - t.Errorf("Could not append second element.") + t.Errorf("Could not append second element: %s.", err.Error()) } // Testing rows - items := col.FindAll() - for i := 0; i < len(items); i++ { + results := dataTypes.FindAll() - item := items[i] + for _, item := range results { for key, _ := range item { @@ -336,7 +343,7 @@ func TestDataTypes(t *testing.T) { "_int16", "_int32", "_int64": - if item.GetInt(key) != int64(data["_int"].(int)) { + if item.GetInt(key) != int64(testData["_int"].(int)) { t.Errorf("Wrong datatype %v.", key) } @@ -350,50 +357,46 @@ func TestDataTypes(t *testing.T) { "_uint64", "_byte", "_rune": - if item.GetInt(key) != int64(data["_uint"].(uint)) { + if item.GetInt(key) != int64(testData["_uint"].(uint)) { t.Errorf("Wrong datatype %v.", key) } // Floating point. case "_float32": case "_float64": - if item.GetFloat(key) != data["_float64"].(float64) { + if item.GetFloat(key) != testData["_float64"].(float64) { t.Errorf("Wrong datatype %v.", key) } // Boolean case "_bool": - if item.GetBool(key) != data["_bool"].(bool) { + if item.GetBool(key) != testData["_bool"].(bool) { t.Errorf("Wrong datatype %v.", key) } // String case "_string": - if item.GetString(key) != data["_string"].(string) { + if item.GetString(key) != testData["_string"].(string) { t.Errorf("Wrong datatype %v.", key) } - // Map - case "_map": - if item.GetTuple(key)["a"] != data["_map"].(sugar.Tuple)["a"] { - t.Errorf("Wrong datatype %v.", key) - } - - // Array - case "_list": - if item.GetList(key)[0] != data["_list"].(sugar.List)[0] { - t.Errorf("Wrong datatype %v.", key) - } + /* + // Map + case "_map": + if item.GetTuple(key)["a"] != testData["_map"].(sugar.Tuple)["a"] { + t.Errorf("Wrong datatype %v.", key) + } - // Time - case "_time": - if item.GetDuration(key).String() != data["_time"].(time.Duration).String() { - t.Errorf("Wrong datatype %v.", key) - } + // Array + case "_list": + if item.GetList(key)[0] != testData["_list"].(sugar.List)[0] { + t.Errorf("Wrong datatype %v.", key) + } + */ // Date case "_date": - if item.GetDate(key).Equal(data["_date"].(time.Time)) == false { + if item.GetDate(key).Equal(testData["_date"].(time.Time)) == false { t.Errorf("Wrong datatype %v.", key) } }