diff --git a/db.go b/db.go index d44de71b317198133f85cae326e7a4ae81f58999..80e6288ddc2f6edf2549a489dd3d1e43475c0fbb 100644 --- a/db.go +++ b/db.go @@ -64,6 +64,14 @@ import ( // db.Cond { "age $lt": 18 } type Cond map[string]interface{} +func (m Cond) Constraints() []builder.Constraint { + c := make([]builder.Constraint, 0, len(m)) + for k, v := range m { + c = append(c, builder.NewConstraint(k, builder.OperatorDefault, v)) + } + return c +} + // Func is a struct that represents database functions. // // Examples: @@ -79,9 +87,21 @@ type Cond map[string]interface{} // // // RTRIM("Hello ") // db.Func{"RTRIM", "Hello "} -type Func struct { - Name string - Args interface{} +type dbFunc struct { + name string + args []interface{} +} + +func (f *dbFunc) Arguments() []interface{} { + return f.args +} + +func (f *dbFunc) Name() string { + return f.name +} + +func Func(name string, args ...interface{}) builder.Function { + return &dbFunc{name: name, args: args} } // And is an array of interfaces that is used to join two or more expressions @@ -104,11 +124,19 @@ type Func struct { // }, // db.Cond{ "last_name": "Mouse" }, // } -type And []interface{} +type And []builder.Constraints // And adds a new expression to an And conditions array. -func (a And) And(exp ...interface{}) And { - return append(a, exp...) +func (a And) And(t ...builder.Constraints) And { + return append(a, t...) +} + +func (a And) Constraints() []builder.Constraints { + return a +} + +func (a And) Operator() builder.ConstraintOperator { + return builder.OperatorAnd } // Or is an array of interfaced that is used to join two or more expressions @@ -122,11 +150,19 @@ func (a And) And(exp ...interface{}) And { // db.Cond{"year": 2012}, // db.Cond{"year": 1987}, // } -type Or []interface{} +type Or []builder.Constraints // Or adds a new expression to an Or conditions array. -func (o Or) Or(exp ...interface{}) Or { - return append(o, exp...) +func (o Or) Or(t ...builder.Constraints) Or { + return append(o, t...) +} + +func (o Or) Constraints() []builder.Constraints { + return o +} + +func (o Or) Operator() builder.ConstraintOperator { + return builder.OperatorOr } // Raw holds chunks of data to be passed to the database without any filtering. @@ -143,8 +179,8 @@ func (o Or) Or(exp ...interface{}) Or { // // // SQL: SOUNDEX('Hello') // Raw{"SOUNDEX('Hello')"} -type Raw struct { - Value interface{} +func Raw(s string) builder.RawValue { + return builder.RawString(s) } // Database is an interface that defines methods that must be provided by @@ -332,15 +368,11 @@ type ConnectionURL interface { // Marshaler is the interface implemented by structs that can marshal // themselves into data suitable for storage. -type Marshaler interface { - MarshalDB() (interface{}, error) -} +type Marshaler builder.Marshaler // Unmarshaler is the interface implemented by structs that can transform // themselves from storage data into a valid value. -type Unmarshaler interface { - UnmarshalDB(interface{}) error -} +type Unmarshaler builder.Unmarshaler // IDSetter is the interface implemented by structs that can set their own ID // after calling Append(). @@ -348,12 +380,6 @@ type IDSetter interface { SetID(map[string]interface{}) error } -// Constrainer is the interface implemented by structs that can delimit -// themselves. -type Constrainer interface { - Constraint() Cond -} - // Int64IDSetter implements a common pattern for setting int64 IDs. type Int64IDSetter interface { SetID(int64) error @@ -377,3 +403,14 @@ type Uint64IDSetter interface { // // UPPERIO_DB_DEBUG=1 ./go-program const EnvEnableDebug = `UPPERIO_DB_DEBUG` + +type Constrainer interface { + Constraints() Cond +} + +var ( + _ = builder.Constraints(Cond{}) + _ = builder.Criteria(And{}) + _ = builder.Criteria(Or{}) + _ = builder.Function(&dbFunc{}) +) diff --git a/error.go b/error.go index 3bd51dcf551a11d78f40b3dbdd452955db96fccb..51a5f9fbdfd6d660ae6d10b4f07753417b84ab16 100644 --- a/error.go +++ b/error.go @@ -23,15 +23,12 @@ package db import ( "errors" + builder "upper.io/builder/meta" ) // Shared error messages. var ( - ErrExpectingPointer = errors.New(`Argument must be an address.`) - ErrExpectingSlicePointer = errors.New(`Argument must be a slice address.`) - ErrExpectingSliceMapStruct = errors.New(`Argument must be a slice address of maps or structs.`) - ErrExpectingMapOrStruct = errors.New(`Argument must be either a map or a struct.`) - ErrNoMoreRows = errors.New(`There are no more rows in this result set.`) + ErrNoMoreRows = builder.ErrNoMoreRows ErrNotConnected = errors.New(`You're currently not connected.`) ErrMissingDatabaseName = errors.New(`Missing database name.`) ErrMissingCollectionName = errors.New(`Missing collection name.`) diff --git a/internal/sqlutil/result/result.go b/internal/sqlutil/result/result.go index 590096bc840e243f7dc6cf8b071e50547c239f4e..36c9ab26b5d12edbaf8e29b0ef90e01f1e3cbf5b 100644 --- a/internal/sqlutil/result/result.go +++ b/internal/sqlutil/result/result.go @@ -39,6 +39,16 @@ type Result struct { conds []interface{} } +func filter(conds []interface{}) []interface{} { + for i := range conds { + switch v := conds[i].(type) { + case db.Constrainer: + conds[i] = v.Constraints() + } + } + return conds +} + // NewResult creates and results a new result set on the given table, this set // is limited by the given sqlgen.Where conditions. func NewResult(b builder.QueryBuilder, table string, conds []interface{}) *Result { @@ -113,7 +123,7 @@ func (r *Result) Next(dst interface{}) (err error) { // Removes the matching items from the collection. func (r *Result) Remove() error { q := r.b.DeleteFrom(r.table). - Where(r.conds...). + Where(filter(r.conds)...). Limit(r.limit) _, err := q.Exec() @@ -133,7 +143,7 @@ func (r *Result) Close() error { func (r *Result) Update(values interface{}) error { q := r.b.Update(r.table). Set(values). - Where(r.conds...). + Where(filter(r.conds)...). Limit(r.limit) _, err := q.Exec() @@ -146,9 +156,9 @@ func (r *Result) Count() (uint64, error) { Count uint64 `db:"_t"` }{} - q := r.b.Select(db.Raw{"count(1) AS _t"}). + q := r.b.Select(db.Raw("count(1) AS _t")). From(r.table). - Where(r.conds...). + Where(filter(r.conds)...). GroupBy(r.groupBy...). Limit(1) @@ -166,7 +176,7 @@ func (r *Result) buildSelect() builder.Selector { q := r.b.Select(r.fields...) q.From(r.table) - q.Where(r.conds...) + q.Where(filter(r.conds)...) q.Limit(r.limit) q.Offset(r.offset) diff --git a/postgresql/database.go b/postgresql/database.go index 2b0584b80f6bf2214d9699e075bad5ff4ba73e10..10cfd544990c3ff96ba14727071aec1022884aec 100644 --- a/postgresql/database.go +++ b/postgresql/database.go @@ -186,7 +186,7 @@ func (d *database) PopulateSchema() (err error) { d.NewSchema() - q := d.Builder().Select(db.Raw{"CURRENT_DATABASE() AS name"}) + q := d.Builder().Select(db.Raw("CURRENT_DATABASE() AS name")) var dbName string diff --git a/postgresql/database_test.go b/postgresql/database_test.go index bb67880904feca14ff86eb9857fb2b0edcfe0ea9..0fcbe29b080b4d4d1ccb67b319090a5899578263 100644 --- a/postgresql/database_test.go +++ b/postgresql/database_test.go @@ -107,12 +107,11 @@ type itemWithKey struct { SomeVal string `db:"some_val"` } -func (item itemWithKey) Constraint() db.Cond { - cond := db.Cond{ +func (item itemWithKey) Constraints() db.Cond { + return db.Cond{ "code": item.Code, "user_id": item.UserID, } - return cond } func (item *itemWithKey) SetID(keys map[string]interface{}) error { @@ -124,6 +123,8 @@ func (item *itemWithKey) SetID(keys map[string]interface{}) error { return errors.New(`Expecting exactly two keys.`) } +var _ = db.Constrainer(itemWithKey{}) + var testValues testValuesStruct func init() { @@ -883,7 +884,7 @@ func TestFunction(t *testing.T) { } // Testing conditions - res = artist.Find(db.Cond{"id": db.Func{"NOT IN", []int{0, -1}}}) + res = artist.Find(db.Cond{"id NOT": db.Func("IN", 0, -1)}) if err = res.One(&rowStruct); err != nil { t.Fatal(err) @@ -899,7 +900,7 @@ func TestFunction(t *testing.T) { // Testing DISTINCT (function) res = artist.Find().Select( - db.Func{`DISTINCT`, `name`}, + db.Func("DISTINCT", "name"), ) if err = res.One(&rowMap); err != nil { @@ -916,7 +917,7 @@ func TestFunction(t *testing.T) { // Testing DISTINCT (raw) res = artist.Find().Select( - db.Raw{`DISTINCT(name)`}, + db.Raw("DISTINCT(name)"), ) if err = res.One(&rowMap); err != nil { @@ -1061,15 +1062,12 @@ func TestGroup(t *testing.T) { } } - // db.Func{"COUNT", 1}, - // db.Func{"SUM", `value`}, - // Testing GROUP BY res := stats.Find().Select( - `numeric`, - db.Raw{`COUNT(1) AS counter`}, - db.Raw{`SUM(value) AS total`}, - ).Group(`numeric`) + "numeric", + db.Raw("COUNT(1) AS counter"), + db.Raw("SUM(value) AS total"), + ).Group("numeric") var results []map[string]interface{}