diff --git a/Makefile b/Makefile index a02ce8f0301cc1fd7e1248d43251735aa6c6f390..b72e16627f8d13f612b250da464c4e889c8b2c32 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ DB_HOST ?= 127.0.0.1 export DB_HOST test: - go test -v -bench=. ./lib/... && \ - go test -v -bench=. ./internal/... && \ + go test -v -benchtime=500ms -bench=. ./lib/... && \ + go test -v -benchtime=500ms -bench=. ./internal/... && \ for ADAPTER in postgresql mysql sqlite ql mongo; do \ $(MAKE) -C $$ADAPTER test; \ done && \ diff --git a/db.go b/db.go index a5de8ad5f7b162cb5beb21030c470746ab4f410b..e831455def6f6f0f107eec4b8bd6c4a6ecdb7fe1 100644 --- a/db.go +++ b/db.go @@ -546,9 +546,24 @@ type Result interface { // set. Select(...interface{}) Result - // Where resets the initial filtering conditions and sets new ones. + // Where discards all the previously set filtering constraints (if any) and + // sets new ones. Commonly used when the conditions of the result depend on + // external parameters that are yet to be evaluated: + // + // res := col.Find() + // + // if ... { + // res.Where(...) + // } else { + // res.Where(...) + // } Where(...interface{}) Result + // And adds more filtering conditions on top of the existing constraints. + // + // res := col.Find(...).And(...) + And(...interface{}) Result + // Group is used to group results that have the same value in the same column // or columns. Group(...interface{}) Result diff --git a/internal/sqladapter/result.go b/internal/sqladapter/result.go index fef6bbbcc6810704f4e568688028fe2b3e237c02..e9a79e9359c0f8d4085cda86e3c936cbbf797a51 100644 --- a/internal/sqladapter/result.go +++ b/internal/sqladapter/result.go @@ -85,6 +85,12 @@ func (r *Result) Where(conds ...interface{}) db.Result { return r } +// And adds more conditions on top of the existing ones. +func (r *Result) And(conds ...interface{}) db.Result { + r.conds = append(r.conds, conds...) + return r +} + // Limit determines the maximum limit of Results to be returned. func (r *Result) Limit(n int) db.Result { r.limit = n diff --git a/internal/sqladapter/testing/adapter.go.tpl b/internal/sqladapter/testing/adapter.go.tpl index 7367a2d45fbf5c456f76a5456d023e0e56b1421e..81774a2c2a526118c31c3281b7d4e85d35da1e83 100644 --- a/internal/sqladapter/testing/adapter.go.tpl +++ b/internal/sqladapter/testing/adapter.go.tpl @@ -94,23 +94,23 @@ func TestPreparedStatementsCache(t *testing.T) { const maxPreparedStatements = 128 * 2 var wg sync.WaitGroup - for i := 0; i < 500; i++ { + for i := 0; i < 1000; i++ { wg.Add(1) go func(i int) { defer wg.Done() // This query is different with each iteration and thus generates a new // prepared statement everytime it's called. - res := sess.Collection("artist").Find().Select(db.Raw(fmt.Sprintf("COUNT(%d)", i))) + res := sess.Collection("artist").Find().Select(db.Raw(fmt.Sprintf("count(%d)", i))) var count map[string]uint64 err := res.One(&count) if err != nil { tFatal(err) } - if sqladapter.NumActiveStatements() > maxPreparedStatements { - tFatal(fmt.Errorf("The number of active statements cannot exceed %d.", maxPreparedStatements)) + if activeStatements := sqladapter.NumActiveStatements(); activeStatements > maxPreparedStatements { + tFatal(fmt.Errorf("The number of active statements cannot exceed %d (got %d).", maxPreparedStatements, activeStatements)) } }(i) - if i%maxPreparedStatements == 0 { + if i%50 == 0 { wg.Wait() } } @@ -368,6 +368,22 @@ func TestInsertIntoArtistsTable(t *testing.T) { count, err := artist.Find().Count() assert.NoError(t, err) assert.Equal(t, uint64(4), count) + + count, err = artist.Find(db.Cond{"name": "Ozzie"}).Count() + assert.NoError(t, err) + assert.Equal(t, uint64(1), count) + + count, err = artist.Find(db.Or(db.Cond{"name": "Ozzie"}, db.Cond{"name": "Flea"})).Count() + assert.NoError(t, err) + assert.Equal(t, uint64(2), count) + + count, err = artist.Find(db.And(db.Cond{"name": "Ozzie"}, db.Cond{"name": "Flea"})).Count() + assert.NoError(t, err) + assert.Equal(t, uint64(0), count) + + count, err = artist.Find(db.Cond{"name": "Ozzie"}).And(db.Cond{"name": "Flea"}).Count() + assert.NoError(t, err) + assert.Equal(t, uint64(0), count) } func TestQueryNonExistentCollection(t *testing.T) { @@ -1176,7 +1192,7 @@ func TestUpdateWithNullColumn(t *testing.T) { assert.NotEqual(t, nil, item.Name) assert.Equal(t, name, *item.Name) - artist.Find(db.Cond{"id": id}).Update(Artist{Name: nil}) + artist.Find(id).Update(Artist{Name: nil}) assert.NoError(t, err) var item2 Artist @@ -1413,34 +1429,6 @@ func TestBuilder(t *testing.T) { assert.NotZero(t, all) } -func TestStressPreparedStatementCache(t *testing.T) { - sess := mustOpen() - defer sess.Close() - - var tMu sync.Mutex - tFatal := func(err error) { - tMu.Lock() - defer tMu.Unlock() - t.Fatal(err) - } - - var wg sync.WaitGroup - - for i := 1; i < 1000; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - res := sess.Collection("artist").Find().Select(db.Raw(fmt.Sprintf("COUNT(%d)", i%5))) - var data map[string]interface{} - if err := res.One(&data); err != nil { - tFatal(err) - } - }(i) - } - - wg.Wait() -} - func TestExhaustConnectionPool(t *testing.T) { if Adapter == "ql" { t.Skip("Currently not supported.") diff --git a/mongo/result.go b/mongo/result.go index 0fa0a794f0509ece898fedc19dbcf687f46b4cbb..f871edab38bdb95e5a64d44bbe225fea719a591e 100644 --- a/mongo/result.go +++ b/mongo/result.go @@ -77,6 +77,19 @@ func (r *result) setCursor() error { return nil } +func (r *result) And(terms ...interface{}) db.Result { + if r.queryChunks.Conditions == nil { + return r.Where(terms) + } + r.queryChunks.Conditions = map[string]interface{}{ + "$and": []interface{}{ + r.queryChunks.Conditions, + r.c.compileQuery(terms...), + }, + } + return r +} + func (r *result) Where(terms ...interface{}) db.Result { r.queryChunks.Conditions = r.c.compileQuery(terms...) return r