diff --git a/db.go b/db.go index 6a881cbe28ac1605dd3d8963a23f64de743c094a..773b11516761eddb38bd61308557d603d10f3841 100644 --- a/db.go +++ b/db.go @@ -22,10 +22,11 @@ */ /* - This package is a wrapper of many third party database drivers. The goal of - this abstraction is to provide a simple, common and consistent layer for - executing operationg among different kinds of databases without the need of - explicit SQL statements. + menteslibres.net/db wraps third party database drivers in an attempt to make + permanent storage with Go as easy as possible. The package features a common, + consistent layer that allows executing operations against different kinds of + databases using Go expressions, without the need for explicit database-specific + instructions. */ package db @@ -36,23 +37,24 @@ import ( ) /* - Handles conditions and operators in an expression. + The db.Cond{} expression is used to filter results in a query, it can be + viewed as a replacement for the SQL "WHERE" clause. Examples: + db.Cond { "age": 18 } // Where age equals 18. - db.Cond { "age": 18 } // Age equals 18 + db.Cond { "age >=": 18 } // Where age is greater than or equal to 18. - db.Cond { "age >=": 18 } // Age greater or equal than 18 (SQL/NoSQL) - - db.Cond { "age $lt": 18 } // Age less than 18 (MongoDB specific) + db.Cond { "age $lt": 18 } // Where age is lower than 18 (MongoDB specific). */ type Cond map[string]interface{} /* - Logical conjuction, accepts db.Cond{}, db.Or{} and other db.And{} expressions. + The db.And() expression is used to glue two or more expressions under logical + conjunction, it accepts db.Cond{}, db.Or() and other db.And() expressions. - Example: + Examples: db.And ( db.Cond { "name": "Peter" }, @@ -60,17 +62,18 @@ type Cond map[string]interface{} ) db.And ( - db.Or { + db.Or ( db.Cond{ "name": "Peter" }, db.Cond{ "name": "Mickey" }, - }, + ), db.Cond{ "last_name": "Mouse" }, ) */ type And []interface{} /* - Logical disjuction, accepts db.Cond{}, db.And{} and other db.Or{} expressions. + The db.Or() expression is used to glue two or more expressions under logical + disjunction, it accepts db.Cond{}, db.And() and other db.Or() expressions. Example: @@ -82,118 +85,20 @@ type And []interface{} type Or []interface{} /* - Determines how results will be sorted. + The db.Sort expression determines how results will be sorted. - Example: - db.Sort { "age": -1 } // Order by age, descendent. - db.Sort { "age": 1 } // Order by age, ascendent. + Examples: - db.Sort { "age": "DESC" } // Order by age, descendent. - db.Sort { "age": "ASC" } // Order by age, ascendent. + db.Sort { "age": -1 } // Order by age, descendent. + db.Sort { "age": 1 } // Order by age, ascendent. */ type Sort map[string]interface{} /* - How rows are going to be modified when using *db.Collection.Update() and - *db.Collection.UpdateAll(). - - Currently unused. - - Example: - - db.Modify { - "$inc": { - "counter": 1 - } - } -*/ -type Modify map[string]interface{} - -/* - Defines a relation between each one of the results of a query and any other - collection. You can relate an item with another item in any other collection - using a condition. - - A constant condition looks like this: + The db.Limit() expression sets the maximum number of rows to be returned in a + query. - db.Cond { "external_field": "value" } - - A dynamic condition looks like this (note the brackets): - - db.Cond { "id": "{foreign_key}" } - - The above condition will match the result where the "id" column is equal to - the "foreign_key" value of the local collection. - - Example: - - // The db.On constraint. - - db.On { - // The external collection. - sess.ExistentCollection("parents"), - - // Reference constraint. - Cond { "id": "{parent_id}" }, - } - - You can use db.On only as a value for db.Relate and db.RelateAll maps. -*/ -type On []interface{} - -/* - A map that defines a one-to-one relation with another table. - - The name of the key will define the name of the relation. A db.On{} constraint - is required. - - Example that relates a result with a row from the "parents" collection. - - // A relation exists where the parents.id column matches the - - // collection.parent_id value. - - db.Relate { - "myparent": On { - db.ExistentCollection("parents"), - Cond { "id": "{parent_id}" }, - } - } - -*/ -type Relate map[string]On - -/* - Like db.Relate but defines a one-to-many relation. - - Example that relates a result with many rows from the "sons" collection: - - - // A relation exists where the sons.parent_id column matches the collection.id - - // value - - db.RelateAll { - "children": db.On { - db.ExistentCollection("sons"), - Cond { "age <=": 18 }, - Cond { "parent_id": "{id}" }, - } - } -*/ -type RelateAll map[string]On - -type Relation struct { - All bool - Name string - Collection Collection - On On -} - -/* - Sets the maximum number of rows to be fetched in a query. - - If no db.Limit is specified, all matches will be returned. + If no db.Limit() is specified, all rows will be returned. Example: @@ -202,9 +107,10 @@ type Relation struct { type Limit uint /* - Sets the number of rows to be skipped before counting the limit in a query. + The db.Offset() expression sets the number of rows to be skipped from a + result. - If no db.Offset is specified no rows will be skipped. + If no db.Offset() is specified, no rows will be skipped. Example: @@ -213,8 +119,8 @@ type Limit uint type Offset uint /* - Determines new values for fields in *db.Collection.Update() and - *db.Collection.UpdateAll() expressions. + The db.Set{} expression is used in *db.Collection.Update()*, it defines new + values for the given fields. Example: @@ -224,25 +130,14 @@ type Offset uint */ type Set map[string]interface{} -/* - Like db.Set{} but it will insert the specified values if no match is found. - - Currently unused. - - db.Upsert { - "name": "New Name", - } -*/ -type Upsert map[string]interface{} - // A query result. type Item map[string]interface{} -// A result id. -type Id string +// A result ID. +type Id interface{} // Connection and authentication data. -type DataSource struct { +type Settings struct { // Host to connect to. Cannot be used if Socket is specified. Host string // Port to connect to. Cannot be used if Socket is specified. @@ -261,165 +156,102 @@ type DataSource struct { // Database methods. type Database interface { - /* - Returns an interface{} to the underlying driver the wrapper uses. Useful - for custom SQL queries. - */ + // Returns the underlying driver the wrapper uses as an interface{}, so you + // can still use database-specific features when you need it. Driver() interface{} - /* - Attempts to open a connection using the db.DataSource data. - */ + // Attempts to open a connection using the current settings. Open() error - /* - Closes the currently active connection to the database, if any. - */ + // Closes the currently active connection to the database. Close() error - /* - Returns a db.Collection struct by name. Returns an error if the collection - does not exists. - */ + // Returns a db.Collection struct by name. Collection(string) (Collection, error) - /* - Returns a db.Collection struct, panics if the collection does not exists. - */ - ExistentCollection(string) Collection - /* - Returns the names of all the collections in the active database. - */ - Collections() []string - - /* - Changes the active database. - */ + // Returns the names of all the collections within the active database. + Collections() ([]string, error) + + // Changes the active database. Use(string) error - /* - Drops the active database. - */ + + // Drops the active database. Drop() error - /* - Sets the connection data. - */ - Setup(DataSource) error + // Sets database connection settings. + Setup(Settings) error - /* - Returns the name of the active database. - */ + // Returns the string name of the active database. Name() string - /* - Starts a transaction block. - */ + // Starts a transaction block (if the database supports transactions). Begin() error - /* - Ends a transaction block. - */ + // Ends a transaction block (if the database supports transactions). End() error } // Collection methods. type Collection interface { - /* - Inserts an item into the collection. Accepts maps or structs only. - */ - Append(...interface{}) ([]Id, error) - - /* - Returns the number of rows that given the given conditions. - */ - Count(...interface{}) (int, error) - - /* - Returns a db.Item map of the first item that matches the given conditions. - */ - Find(...interface{}) (Item, error) - - /* - Returns a []db.Item slice of all the items that match the given conditions. - - Useful for small datasets. - */ - FindAll(...interface{}) ([]Item, error) - - /* - Finds a matching row and sets new values for the given fields. - */ - Update(interface{}, interface{}) error - - /* - Returns true if the collection exists. - */ - Exists() bool - /* - Returns a db.Result that can be used for iterating over the rows. + // Inserts a new item into the collection. Can work with maps or structs. + Append(interface{}) (Id, error) - Useful for large datasets. - */ - Query(...interface{}) (Result, error) + // Returns true if the collection exists. + Exists() bool - /* - Deletes all the rows that match the given conditions. - */ - Remove(...interface{}) error + // Creates a filter with the given conditions and returns a result set. + Filter(...interface{}) (Result, error) - /* - Deletes all the rows in the collection. - */ + // Truncates the collection. Truncate() error - /* - Returns the name of the collection. - */ + // Returns the string name of the collection. Name() string } // Result methods. type Result interface { - /* - Fetches all the results of the query into the given pointer. - Accepts a pointer to slice of maps or structs. - */ - All(interface{}) error + // Removes all items within the result set. + Remove() - /* - Fetches the first result of the query into the given pointer and discards - the rest. + // Updates all items within the result set. + Update(interface{}) - Accepts a pointer to map or struct. - */ - One(interface{}) error + // Counts all items within the result set. + Count() (int, error) - /* - Fetches the next result of the query into the given pointer. Returns error if - there are no more results. + // Fetches all the results of the query into the given pointer. + // + // Accepts a pointer to slice of maps or structs. + All(interface{}) error - Warning: If you're only using part of these results you must manually Close() - the result. + // Fetches the first result of the query into the given pointer and discards + // the rest. + // + // Accepts a pointer to map or struct. + One(interface{}) error - Accepts a pointer to map or struct. - */ + // Fetches the next result of the query into the given pointer. Returns error if + // there are no more results. + // Warning: If you're only using part of these results you must manually Close() + // the result. + // + // Accepts a pointer to map or struct. Next(interface{}) error - /* - Closes the result. - */ + // Closes the resultset. Close() error } // Specifies which fields will be returned in a query. -type Fields []string +// type Fields []string // These are internal variables. -type MultiFlag bool -type SqlValues []string -type SqlArgs []string +//type MultiFlag bool +//type SqlValues []string +//type SqlArgs []string // Error messages var ( @@ -441,9 +273,7 @@ var ( // Registered wrappers. var wrappers = make(map[string]Database) -/* - Registers a database wrapper with an unique name. -*/ +// Registers a database wrapper with a unique name. func Register(name string, driver Database) { if name == "" { @@ -457,10 +287,9 @@ func Register(name string, driver Database) { wrappers[name] = driver } -/* - Opens a database using the named driver and the db.DataSource settings. -*/ -func Open(name string, settings DataSource) (Database, error) { +// Configures a connection to a database using the named wrapper and the given +// settings. +func Open(name string, settings Settings) (Database, error) { driver, ok := wrappers[name] @@ -469,8 +298,11 @@ func Open(name string, settings DataSource) (Database, error) { } // Creating a new connection everytime Open() is called. - conn := reflect.New(reflect.ValueOf(driver).Elem().Type()).Interface().(Database) + t := reflect.ValueOf(driver).Elem().Type() + + conn := reflect.New(t).Interface().(Database) + // Setting up the connection with the given source. err := conn.Setup(settings) if err != nil { diff --git a/util/main.go b/util/main.go index 1259fb2664cff3db2054ff05ab960d3c3ebc3f37..3128f06af865eb8b65ddf1f033829a31e7c34b2f 100644 --- a/util/main.go +++ b/util/main.go @@ -24,7 +24,6 @@ package util import ( - "fmt" "menteslibres.net/gosexy/db" "menteslibres.net/gosexy/to" "reflect" @@ -44,29 +43,6 @@ type C struct { SetName string } -func (self *C) RelationCollection(name string, terms db.On) (db.Collection, error) { - - var err error - var col db.Collection - - for _, v := range terms { - - switch t := v.(type) { - case db.Collection: - col = t - } - } - - if col == nil { - col, err = self.DB.Collection(name) - if err != nil || col == nil { - return nil, fmt.Errorf("Failed relation %s: %s", name, err.Error()) - } - } - - return col, nil -} - func columnCompare(s string) string { return strings.ToLower(columnCompareExclude.ReplaceAllString(s, "")) } @@ -138,168 +114,6 @@ func (self *C) Name() string { return self.SetName } -func fetchItemRelations(itemv reflect.Value, relations []db.Relation, convertFn func(interface{}) interface{}) error { - var err error - - itemk := itemv.Type().Kind() - - for _, relation := range relations { - - terms := make([]interface{}, len(relation.On)) - - for j, term := range relation.On { - switch t := term.(type) { - // Just waiting for db.Cond statements. - case db.Cond: - for k, v := range t { - switch s := v.(type) { - case string: - matches := extRelationPattern.FindStringSubmatch(s) - if len(matches) > 1 { - extkey := matches[1] - var val reflect.Value - switch itemk { - case reflect.Struct: - index := GetStructFieldIndex(itemv.Type(), extkey) - if index == nil { - continue - } else { - val = itemv.FieldByIndex(index) - } - case reflect.Map: - val = itemv.MapIndex(reflect.ValueOf(extkey)) - } - if val.IsValid() { - term = db.Cond{k: convertFn(val.Interface())} - } - } - } - } - case db.Collection: - relation.Collection = t - } - terms[j] = term - } - - keyv := reflect.ValueOf(relation.Name) - - switch itemk { - case reflect.Struct: - var val reflect.Value - - index := GetStructFieldIndex(itemv.Type(), relation.Name) - - if index == nil { - continue - } else { - val = itemv.FieldByIndex(index) - } - - if val.IsValid() { - var res db.Result - - res, err = relation.Collection.Query(terms...) - - if err != nil { - return err - } - - p := reflect.New(val.Type()) - q := p.Interface() - - if relation.All == true { - err = res.All(q) - } else { - err = res.One(q) - } - - if err != nil { - return err - } - - val.Set(reflect.Indirect(p)) - - } - case reflect.Map: - var err error - var res db.Result - var p reflect.Value - - res, err = relation.Collection.Query(terms...) - - if err != nil { - return err - } - - // Executing external query. - if relation.All == true { - var items []map[string]interface{} - err = res.All(&items) - p = reflect.ValueOf(items) - } else { - var item map[string]interface{} - err = res.One(&item) - p = reflect.ValueOf(item) - } - - if err != nil { - return err - } - - itemv.SetMapIndex(keyv, p) - } - - } - - return nil -} - -func (self *C) FetchRelation(dst interface{}, relations []db.Relation, convertFn func(interface{}) interface{}) error { - var err error - - if relations == nil { - return nil - } - - dstv := reflect.ValueOf(dst) - - if dstv.Kind() != reflect.Ptr || dstv.IsNil() { - return db.ErrExpectingPointer - } - - err = fetchItemRelations(dstv.Elem(), relations, convertFn) - - if err != nil { - return err - } - - return nil -} - -func (self *C) FetchRelations(dst interface{}, relations []db.Relation, convertFn func(interface{}) interface{}) error { - var err error - - if relations == nil { - return nil - } - - err = ValidateSliceDestination(dst) - - dstv := reflect.ValueOf(dst) - itemv := dstv.Elem() - - // Iterate over results. - for i := 0; i < dstv.Elem().Len(); i++ { - item := itemv.Index(i) - err = fetchItemRelations(item, relations, convertFn) - if err != nil { - return err - } - } - - return nil -} - func ValidateSliceDestination(dst interface{}) error { var dstv reflect.Value diff --git a/util/sqlutil/main.go b/util/sqlutil/main.go index cd05fedde332bcc2211ed99b7374befd7c564542..1296afc455522825d9103195c8c6775cd9f5a463 100644 --- a/util/sqlutil/main.go +++ b/util/sqlutil/main.go @@ -43,11 +43,8 @@ type QueryChunks struct { Limit string Offset string Sort string - Relate db.Relate - RelateAll db.RelateAll - Relations []db.Relation Conditions string - Arguments db.SqlArgs + Arguments []string } func (self *T) ColumnLike(s string) string { @@ -324,9 +321,6 @@ func (self *T) FieldValues(item interface{}, convertFn func(interface{}) string) } func NewQueryChunks() *QueryChunks { - self := &QueryChunks{ - Relate: make(db.Relate), - RelateAll: make(db.RelateAll), - } + self := &QueryChunks{} return self } diff --git a/util/sqlutil/result.go b/util/sqlutil/result.go index 02a8d1f12e31da87f32762f4e676d4808e259588..70214b5035b5d9e88222b6121c398103b2ec00ef 100644 --- a/util/sqlutil/result.go +++ b/util/sqlutil/result.go @@ -25,13 +25,11 @@ package sqlutil import ( "database/sql" - "menteslibres.net/gosexy/db" ) type Result struct { - Rows *sql.Rows - Table *T - Relations []db.Relation + Rows *sql.Rows + Table *T } func (self *Result) FetchAll(dst interface{}, convertFn func(interface{}) interface{}) error { @@ -44,12 +42,6 @@ func (self *Result) FetchAll(dst interface{}, convertFn func(interface{}) interf return err } - err = self.Table.FetchRelations(dst, self.Relations, convertFn) - - if err != nil { - return err - } - return nil } @@ -62,12 +54,6 @@ func (self *Result) FetchNext(dst interface{}, convertFn func(interface{}) inter return err } - err = self.Table.FetchRelation(dst, self.Relations, convertFn) - - if err != nil { - return err - } - return nil }