From d056320c4dc58cdf5fd0466feb8e4eda6a95b962 Mon Sep 17 00:00:00 2001 From: Alexander Simmerl Date: Mon, 21 Aug 2017 16:54:33 +0200 Subject: [PATCH] Implement multi count for objects --- core/comment.go | 15 ++++---- core/comment_test.go | 18 +++++----- core/feed.go | 4 +-- core/pipieline_test.go | 4 +-- core/post.go | 25 +++++-------- handler/http/post.go | 2 +- service/object/cache.go | 4 +++ service/object/helper_test.go | 56 +++++++++++++++++++++++++++++ service/object/instrumentation.go | 8 +++++ service/object/logging.go | 21 ++++++++++- service/object/mem.go | 5 +++ service/object/object.go | 11 ++++++ service/object/postgres.go | 59 +++++++++++++++++++++++++++++++ service/object/postgres_test.go | 4 +++ service/object/sourcing.go | 4 +++ service/reaction/postgres.go | 10 +----- 16 files changed, 201 insertions(+), 49 deletions(-) diff --git a/core/comment.go b/core/comment.go index 713afe3..577ba39 100644 --- a/core/comment.go +++ b/core/comment.go @@ -8,9 +8,6 @@ import ( ) const ( - // TypeComment identifies a comment object. - TypeComment = "tg_comment" - attachmentContent = "content" ) @@ -76,7 +73,7 @@ func CommentCreate( OwnerID: origin.UserID, Owned: true, Private: input.Private, - Type: TypeComment, + Type: object.TypeComment, Visibility: post.Visibility, } @@ -120,7 +117,7 @@ func CommentDelete( origin, }, Types: []string{ - TypeComment, + object.TypeComment, }, Owned: &defaultOwned, }) @@ -188,7 +185,7 @@ func CommentList( postID, }, Types: []string{ - TypeComment, + object.TypeComment, }, Owned: &defaultOwned, }) @@ -237,7 +234,7 @@ func CommentRetrieve( origin, }, Types: []string{ - TypeComment, + object.TypeComment, }, Owned: &defaultOwned, }) @@ -286,7 +283,7 @@ func CommentUpdate( }, Owned: &defaultOwned, Types: []string{ - TypeComment, + object.TypeComment, }, }) if err != nil { @@ -316,7 +313,7 @@ func CommentUpdate( // IsComment indicates if Object is a comment. func IsComment(o *object.Object) bool { - if o.Type != TypeComment { + if o.Type != object.TypeComment { return false } diff --git a/core/comment_test.go b/core/comment_test.go index c3d637a..049248d 100644 --- a/core/comment_test.go +++ b/core/comment_test.go @@ -37,7 +37,7 @@ func TestCommentCreate(t *testing.T) { ID: &created.ID, Owned: &defaultOwned, Types: []string{ - TypeComment, + object.TypeComment, }, }) if err != nil { @@ -121,7 +121,7 @@ func TestCommentDelete(t *testing.T) { ID: &created.ID, Owned: &defaultOwned, Types: []string{ - TypeComment, + object.TypeComment, }, }) if err != nil { @@ -245,7 +245,7 @@ func TestCommentUpdate(t *testing.T) { ID: &created.ID, Owned: &defaultOwned, Types: []string{ - TypeComment, + object.TypeComment, }, }) if err != nil { @@ -303,7 +303,7 @@ func testComment(ownerID uint64, post *object.Object) *object.Object { ObjectID: post.ID, OwnerID: ownerID, Owned: true, - Type: TypeComment, + Type: object.TypeComment, Visibility: post.Visibility, } } @@ -319,7 +319,7 @@ func testCommentSet(ownerID uint64, post *object.Object) []*object.Object { ObjectID: post.ID, OwnerID: ownerID, Owned: true, - Type: TypeComment, + Type: object.TypeComment, Visibility: post.Visibility, }, { @@ -331,7 +331,7 @@ func testCommentSet(ownerID uint64, post *object.Object) []*object.Object { ObjectID: post.ID, OwnerID: ownerID + 1, Owned: true, - Type: TypeComment, + Type: object.TypeComment, Visibility: post.Visibility, }, { @@ -343,7 +343,7 @@ func testCommentSet(ownerID uint64, post *object.Object) []*object.Object { ObjectID: post.ID, OwnerID: ownerID - 1, Owned: true, - Type: TypeComment, + Type: object.TypeComment, Visibility: post.Visibility, }, { @@ -355,7 +355,7 @@ func testCommentSet(ownerID uint64, post *object.Object) []*object.Object { ObjectID: post.ID, OwnerID: ownerID, Owned: true, - Type: TypeComment, + Type: object.TypeComment, Visibility: post.Visibility, }, { @@ -367,7 +367,7 @@ func testCommentSet(ownerID uint64, post *object.Object) []*object.Object { ObjectID: post.ID, OwnerID: ownerID, Owned: true, - Type: TypeComment, + Type: object.TypeComment, Visibility: post.Visibility, }, } diff --git a/core/feed.go b/core/feed.go index a93a11c..1354ab7 100644 --- a/core/feed.go +++ b/core/feed.go @@ -1053,7 +1053,7 @@ func sourceComment( ObjectIDs: postIDs, Owned: &defaultOwned, Types: []string{ - TypeComment, + object.TypeComment, }, }) if err != nil { @@ -1075,7 +1075,7 @@ func sourceComment( ID: id, ObjectID: comment.ObjectID, Owned: true, - Type: TypeComment, + Type: object.TypeComment, UserID: comment.OwnerID, Visibility: event.VisibilityPrivate, CreatedAt: comment.CreatedAt, diff --git a/core/pipieline_test.go b/core/pipieline_test.go index 6d6f8c2..ba8ad89 100644 --- a/core/pipieline_test.go +++ b/core/pipieline_test.go @@ -539,7 +539,7 @@ func TestPipelineObjectCondObjectOwner(t *testing.T) { Criteria: &rule.CriteriaObject{ New: &object.QueryOptions{ Owned: &defaultOwned, - Types: []string{TypeComment}, + Types: []string{object.TypeComment}, }, Old: nil, }, @@ -623,7 +623,7 @@ func TestPipelineObjectCondOwner(t *testing.T) { Criteria: &rule.CriteriaObject{ New: &object.QueryOptions{ Owned: &defaultOwned, - Types: []string{TypeComment}, + Types: []string{object.TypeComment}, }, Old: nil, }, diff --git a/core/post.go b/core/post.go index 0805d8a..284567b 100644 --- a/core/post.go +++ b/core/post.go @@ -35,7 +35,7 @@ type Post struct { // PostCounts bundles all connected entity counts. type PostCounts struct { - Comments int + Comments uint64 Likes int ReactionCounts reaction.Counts } @@ -555,7 +555,12 @@ func enrichCounts( currentApp *app.App, ps PostList, ) error { - countsMap, err := reactions.CountMulti(currentApp.Namespace(), reaction.QueryOptions{ + commentsMap, err := objects.CountMulti(currentApp.Namespace(), ps.objectIDs()...) + if err != nil { + return err + } + + reactionsMap, err := reactions.CountMulti(currentApp.Namespace(), reaction.QueryOptions{ Deleted: &defaultDeleted, ObjectIDs: ps.objectIDs(), }) @@ -564,21 +569,9 @@ func enrichCounts( } for _, p := range ps { - comments, err := objects.Count(currentApp.Namespace(), object.QueryOptions{ - ObjectIDs: []uint64{ - p.ID, - }, - Types: []string{ - TypeComment, - }, - }) - if err != nil { - return err - } - p.Counts = PostCounts{ - Comments: comments, - ReactionCounts: countsMap[p.ObjectID], + Comments: commentsMap[p.ObjectID].Comments, + ReactionCounts: reactionsMap[p.ObjectID], } } diff --git a/handler/http/post.go b/handler/http/post.go index 7e9b842..c47c70f 100644 --- a/handler/http/post.go +++ b/handler/http/post.go @@ -445,7 +445,7 @@ func (p *payloadPosts) MarshalJSON() ([]byte, error) { } type postCounts struct { - Comments int `json:"comments"` + Comments uint64 `json:"comments"` Likes int `json:"likes"` Reactions reactionCounts `json:"reactions"` } diff --git a/service/object/cache.go b/service/object/cache.go index 6118703..467cfbe 100644 --- a/service/object/cache.go +++ b/service/object/cache.go @@ -50,6 +50,10 @@ func (s *cacheService) Count(ns string, opts QueryOptions) (int, error) { return count, err } +func (s *cacheService) CountMulti(ns string, objectIDs ...uint64) (m CountsMap, err error) { + return s.next.CountMulti(ns, objectIDs...) +} + func (s *cacheService) Put(ns string, input *Object) (output *Object, err error) { key := cacheCountKey(QueryOptions{ Types: []string{ diff --git a/service/object/helper_test.go b/service/object/helper_test.go index 719923c..9b59a2e 100644 --- a/service/object/helper_test.go +++ b/service/object/helper_test.go @@ -1,6 +1,8 @@ package object import ( + "math/rand" + "reflect" "testing" "time" ) @@ -279,6 +281,60 @@ func testServiceCount(t *testing.T, p prepareFunc) { } } +func testServiceCountMulti(t *testing.T, p prepareFunc) { + var ( + namespace = "service_count_multi" + service = p(namespace, t) + objectIDs = []uint64{ + uint64(rand.Int63()), + uint64(rand.Int63()), + uint64(rand.Int63()), + } + ownerID = uint64(rand.Int63()) + want = CountsMap{} + ) + + for _, oid := range objectIDs { + article := *testArticle + + article.ObjectID = oid + article.OwnerID = ownerID + + _, err := service.Put(namespace, &article) + if err != nil { + t.Fatal(err) + } + + it := rand.Intn(12) + + for i := 0; i < it; i++ { + _, err = service.Put(namespace, &Object{ + ObjectID: oid, + Owned: true, + OwnerID: uint64(rand.Int63()), + Type: TypeComment, + Visibility: VisibilityPublic, + }) + if err != nil { + t.Fatal(err) + } + } + + want[oid] = Counts{ + Comments: uint64(it), + } + } + + have, err := service.CountMulti(namespace, objectIDs...) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(have, want) { + t.Errorf("have %v, want %v", have, want) + } +} + func testServiceQuery(t *testing.T, p prepareFunc) { var ( namespace = "service_query" diff --git a/service/object/instrumentation.go b/service/object/instrumentation.go index c266659..6a0e68d 100644 --- a/service/object/instrumentation.go +++ b/service/object/instrumentation.go @@ -48,6 +48,14 @@ func (s *instrumentService) Count(ns string, opts QueryOptions) (count int, err return s.next.Count(ns, opts) } +func (s *instrumentService) CountMulti(ns string, objectIDs ...uint64) (m CountsMap, err error) { + defer func(begin time.Time) { + s.track("CountMulti", ns, begin, err) + }(time.Now()) + + return s.next.CountMulti(ns, objectIDs...) +} + func (s *instrumentService) Put(ns string, object *Object) (o *Object, err error) { defer func(begin time.Time) { s.track("put", ns, begin, err) diff --git a/service/object/logging.go b/service/object/logging.go index 966299e..c2d8a05 100644 --- a/service/object/logging.go +++ b/service/object/logging.go @@ -30,7 +30,7 @@ func (s *logService) Count(ns string, opts QueryOptions) (count int, err error) ps := []interface{}{ "count", count, "duration_ns", time.Since(begin).Nanoseconds(), - "method", "Query", + "method", "Count", "namespace", ns, "opts", opts, } @@ -45,6 +45,25 @@ func (s *logService) Count(ns string, opts QueryOptions) (count int, err error) return s.next.Count(ns, opts) } +func (s *logService) CountMulti(ns string, objectIDs ...uint64) (m CountsMap, err error) { + defer func(begin time.Time) { + ps := []interface{}{ + "duration_ns", time.Since(begin).Nanoseconds(), + "ids_count", len(objectIDs), + "method", "CountMulti", + "namespace", ns, + } + + if err != nil { + ps = append(ps, "err", err) + } + + _ = s.logger.Log(ps...) + }(time.Now()) + + return s.next.CountMulti(ns, objectIDs...) +} + func (s *logService) Put(ns string, input *Object) (output *Object, err error) { defer func(begin time.Time) { ps := []interface{}{ diff --git a/service/object/mem.go b/service/object/mem.go index 038dcda..35179b6 100644 --- a/service/object/mem.go +++ b/service/object/mem.go @@ -1,6 +1,7 @@ package object import ( + "fmt" "math" "sort" "time" @@ -32,6 +33,10 @@ func (s *memService) Count(ns string, opts QueryOptions) (int, error) { return len(filterList(listFromMap(bucket), opts)), nil } +func (s *memService) CountMulti(ns string, objectIDs ...uint64) (m CountsMap, err error) { + return nil, fmt.Errorf("memService.CountMulti not implemented") +} + func (s *memService) Put(ns string, object *Object) (*Object, error) { if err := object.Validate(); err != nil { return nil, err diff --git a/service/object/object.go b/service/object/object.go index 99cf821..edbf50e 100644 --- a/service/object/object.go +++ b/service/object/object.go @@ -27,6 +27,8 @@ const ( StateDeclined ) +const TypeComment = "tg_comment" + // Visibility variants available for Objects. const ( VisibilityPrivate Visibility = (iota + 1) * 10 @@ -110,6 +112,14 @@ func (c Contents) Validate() error { return nil } +// Counts bundles all Object counts by type. +type Counts struct { + Comments uint64 +} + +// CountsMap is the association of an object id to Counts. +type CountsMap map[uint64]Counts + // List is an Object collection. type List []*Object @@ -300,6 +310,7 @@ type Service interface { service.Lifecycle Count(namespace string, opts QueryOptions) (int, error) + CountMulti(namespace string, objectIds ...uint64) (CountsMap, error) Put(namespace string, object *Object) (*Object, error) Query(namespace string, opts QueryOptions) (List, error) } diff --git a/service/object/postgres.go b/service/object/postgres.go index 0b01522..0dc2dd0 100644 --- a/service/object/postgres.go +++ b/service/object/postgres.go @@ -100,6 +100,65 @@ func (s *pgService) Count(ns string, opts QueryOptions) (int, error) { return s.countObjects(ns, where, params...) } +func (s *pgService) CountMulti( + ns string, + objectIDs ...uint64, +) (m CountsMap, err error) { + tx, err := s.db.Beginx() + if err != nil { + return nil, err + } + + defer func(tx *sqlx.Tx) { + if err != nil { + _ = tx.Rollback() + } + }(tx) + + var ( + countsMap = CountsMap{} + owned = true + ) + + for _, oid := range objectIDs { + where, params, err := convertOpts(QueryOptions{ + Deleted: false, + ObjectIDs: []uint64{ + oid, + }, + Owned: &owned, + Types: []string{ + TypeComment, + }, + }, orderNone) + if err != nil { + return nil, err + } + + var ( + query = fmt.Sprintf(pgCountObjects, ns, where) + + count uint64 + ) + + err = tx.Get(&count, query, params...) + if err != nil { + return nil, err + } + + countsMap[oid] = Counts{ + Comments: uint64(count), + } + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + return countsMap, nil +} + func (s *pgService) Put(ns string, object *Object) (*Object, error) { var ( now = time.Now().UTC() diff --git a/service/object/postgres_test.go b/service/object/postgres_test.go index 8e9962b..1fbf9e1 100644 --- a/service/object/postgres_test.go +++ b/service/object/postgres_test.go @@ -19,6 +19,10 @@ func TestPostgresServiceCount(t *testing.T) { testServiceCount(t, preparePostgres) } +func TestPostgresServiceCountMulti(t *testing.T) { + testServiceCountMulti(t, preparePostgres) +} + func TestPostgresServicePut(t *testing.T) { var ( namespace = "service_put" diff --git a/service/object/sourcing.go b/service/object/sourcing.go index b4860af..9f62c9a 100644 --- a/service/object/sourcing.go +++ b/service/object/sourcing.go @@ -20,6 +20,10 @@ func (s *sourcingService) Count(ns string, opts QueryOptions) (int, error) { return s.service.Count(ns, opts) } +func (s *sourcingService) CountMulti(ns string, objectIDs ...uint64) (m CountsMap, err error) { + return s.service.CountMulti(ns, objectIDs...) +} + func (s *sourcingService) Put( ns string, input *Object, diff --git a/service/reaction/postgres.go b/service/reaction/postgres.go index 6a90855..4539091 100644 --- a/service/reaction/postgres.go +++ b/service/reaction/postgres.go @@ -130,12 +130,8 @@ func (s *pgService) CountMulti(ns string, opts QueryOptions) (m CountsMap, err e query := fmt.Sprintf(pgCountReactionsMulti, ns) - rows, err := s.db.Query(query, params...) + rows, err := tx.Query(query, params...) if err != nil { - if rerr := tx.Rollback(); rerr != nil { - return nil, err - } - return nil, err } defer rows.Close() @@ -168,10 +164,6 @@ func (s *pgService) CountMulti(ns string, opts QueryOptions) (m CountsMap, err e } if err := rows.Err(); err != nil { - if rerr := tx.Rollback(); rerr != nil { - return nil, err - } - return nil, err }