optimise topic lists by caching common qcounts for getList stmts

recalc forum topic counts
reduce thaw period to 3
cache top 8 forums instead of 5
optimise GetListByForum with zero topics
add Each method to ForumStore
This commit is contained in:
Azareal 2020-03-09 13:51:44 +10:00
parent 42a95d8597
commit b04d77d7b6
7 changed files with 178 additions and 59 deletions

View File

@ -28,6 +28,7 @@ var ErrNoDeleteReports = errors.New("You cannot delete the Reports forum")
// ForumStore is an interface for accessing the forums and the metadata stored on them // ForumStore is an interface for accessing the forums and the metadata stored on them
type ForumStore interface { type ForumStore interface {
LoadForums() error LoadForums() error
Each(h func(*Forum) error) error
DirtyGet(id int) *Forum DirtyGet(id int) *Forum
Get(id int) (*Forum, error) Get(id int) (*Forum, error)
BypassGet(id int) (*Forum, error) BypassGet(id int) (*Forum, error)
@ -35,10 +36,10 @@ type ForumStore interface {
Reload(id int) error // ? - Should we move this to ForumCache? It might require us to do some unnecessary casting though Reload(id int) error // ? - Should we move this to ForumCache? It might require us to do some unnecessary casting though
//Update(Forum) error //Update(Forum) error
Delete(id int) error Delete(id int) error
AddTopic(tid int, uid int, fid int) error AddTopic(tid, uid, fid int) error
RemoveTopic(fid int) error RemoveTopic(fid int) error
RemoveTopics(fid, count int) error RemoveTopics(fid, count int) error
UpdateLastTopic(tid int, uid int, fid int) error UpdateLastTopic(tid, uid, fid int) error
Exists(id int) bool Exists(id int) bool
GetAll() ([]*Forum, error) GetAll() ([]*Forum, error)
GetAllIDs() ([]int, error) GetAllIDs() ([]int, error)
@ -46,7 +47,7 @@ type ForumStore interface {
GetAllVisibleIDs() ([]int, error) GetAllVisibleIDs() ([]int, error)
//GetChildren(parentID int, parentType string) ([]*Forum,error) //GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error) //GetFirstChild(parentID int, parentType string) (*Forum,error)
Create(name string, desc string, active bool, preset string) (int, error) Create(name, desc string, active bool, preset string) (int, error)
UpdateOrder(updateMap map[int]int) error UpdateOrder(updateMap map[int]int) error
Count() int Count() int
@ -82,16 +83,16 @@ func NewMemoryForumStore() (*MemoryForumStore, error) {
f := "forums" f := "forums"
// TODO: Do a proper delete // TODO: Do a proper delete
return &MemoryForumStore{ return &MemoryForumStore{
get: acc.Select(f).Columns("name, desc, tmpl, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid = ?").Prepare(), get: acc.Select(f).Columns("name, desc, tmpl, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid=?").Prepare(),
getAll: acc.Select(f).Columns("fid, name, desc, tmpl, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Orderby("order ASC, fid ASC").Prepare(), getAll: acc.Select(f).Columns("fid, name, desc, tmpl, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Orderby("order ASC, fid ASC").Prepare(),
delete: acc.Update(f).Set("name='',active=0").Where("fid = ?").Prepare(), delete: acc.Update(f).Set("name='',active=0").Where("fid=?").Prepare(),
create: acc.Insert(f).Columns("name, desc, tmpl, active, preset").Fields("?,?,'',?,?").Prepare(), create: acc.Insert(f).Columns("name, desc, tmpl, active, preset").Fields("?,?,'',?,?").Prepare(),
count: acc.Count(f).Where("name != ''").Prepare(), count: acc.Count(f).Where("name != ''").Prepare(),
updateCache: acc.Update(f).Set("lastTopicID = ?, lastReplyerID = ?").Where("fid = ?").Prepare(), updateCache: acc.Update(f).Set("lastTopicID=?, lastReplyerID=?").Where("fid=?").Prepare(),
addTopics: acc.Update(f).Set("topicCount=topicCount+?").Where("fid = ?").Prepare(), addTopics: acc.Update(f).Set("topicCount=topicCount+?").Where("fid=?").Prepare(),
removeTopics: acc.Update(f).Set("topicCount=topicCount-?").Where("fid = ?").Prepare(), removeTopics: acc.Update(f).Set("topicCount=topicCount-?").Where("fid=?").Prepare(),
lastTopic: acc.Select("topics").Columns("tid").Where("parentID = ?").Orderby("lastReplyAt DESC, createdAt DESC").Limit("1").Prepare(), lastTopic: acc.Select("topics").Columns("tid").Where("parentID=?").Orderby("lastReplyAt DESC, createdAt DESC").Limit("1").Prepare(),
updateOrder: acc.Update(f).Set("order = ?").Where("fid = ?").Prepare(), updateOrder: acc.Update(f).Set("order=?").Where("fid=?").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -99,10 +100,10 @@ func NewMemoryForumStore() (*MemoryForumStore, error) {
// TODO: Add support for subforums // TODO: Add support for subforums
func (s *MemoryForumStore) LoadForums() error { func (s *MemoryForumStore) LoadForums() error {
var forumView []*Forum var forumView []*Forum
addForum := func(forum *Forum) { addForum := func(f *Forum) {
s.forums.Store(forum.ID, forum) s.forums.Store(f.ID, f)
if forum.Active && forum.Name != "" && forum.ParentType == "" { if f.Active && f.Name != "" && f.ParentType == "" {
forumView = append(forumView, forum) forumView = append(forumView, f)
} }
} }
@ -140,11 +141,11 @@ func (s *MemoryForumStore) LoadForums() error {
// ? - Will this be hit a lot by plugin_guilds? // ? - Will this be hit a lot by plugin_guilds?
func (s *MemoryForumStore) rebuildView() { func (s *MemoryForumStore) rebuildView() {
var forumView []*Forum var forumView []*Forum
s.forums.Range(func(_ interface{}, value interface{}) bool { s.forums.Range(func(_, val interface{}) bool {
forum := value.(*Forum) f := val.(*Forum)
// ? - ParentType blank means that it doesn't have a parent // ? - ParentType blank means that it doesn't have a parent
if forum.Active && forum.Name != "" && forum.ParentType == "" { if f.Active && f.Name != "" && f.ParentType == "" {
forumView = append(forumView, forum) forumView = append(forumView, f)
} }
return true return true
}) })
@ -153,6 +154,17 @@ func (s *MemoryForumStore) rebuildView() {
TopicListThaw.Thaw() TopicListThaw.Thaw()
} }
func (s *MemoryForumStore) Each(h func(*Forum) error) (err error) {
s.forums.Range(func(_, val interface{}) bool {
err = h(val.(*Forum))
if err != nil {
return false
}
return true
})
return err
}
func (s *MemoryForumStore) DirtyGet(id int) *Forum { func (s *MemoryForumStore) DirtyGet(id int) *Forum {
fint, ok := s.forums.Load(id) fint, ok := s.forums.Load(id)
if !ok || fint.(*Forum).Name == "" { if !ok || fint.(*Forum).Name == "" {
@ -208,11 +220,11 @@ func (s *MemoryForumStore) BypassGet(id int) (*Forum, error) {
func (s *MemoryForumStore) BulkGetCopy(ids []int) (forums []Forum, err error) { func (s *MemoryForumStore) BulkGetCopy(ids []int) (forums []Forum, err error) {
forums = make([]Forum, len(ids)) forums = make([]Forum, len(ids))
for i, id := range ids { for i, id := range ids {
forum, err := s.Get(id) f, err := s.Get(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
forums[i] = forum.Copy() forums[i] = f.Copy()
} }
return forums, nil return forums, nil
} }
@ -226,16 +238,16 @@ func (s *MemoryForumStore) Reload(id int) error {
return nil return nil
} }
func (s *MemoryForumStore) CacheSet(forum *Forum) error { func (s *MemoryForumStore) CacheSet(f *Forum) error {
s.forums.Store(forum.ID, forum) s.forums.Store(f.ID, f)
s.rebuildView() s.rebuildView()
return nil return nil
} }
// ! Has a randomised order // ! Has a randomised order
func (s *MemoryForumStore) GetAll() (forumView []*Forum, err error) { func (s *MemoryForumStore) GetAll() (forumView []*Forum, err error) {
s.forums.Range(func(_ interface{}, value interface{}) bool { s.forums.Range(func(_, val interface{}) bool {
forumView = append(forumView, value.(*Forum)) forumView = append(forumView, val.(*Forum))
return true return true
}) })
sort.Sort(SortForum(forumView)) sort.Sort(SortForum(forumView))
@ -244,8 +256,8 @@ func (s *MemoryForumStore) GetAll() (forumView []*Forum, err error) {
// ? - Can we optimise the sorting? // ? - Can we optimise the sorting?
func (s *MemoryForumStore) GetAllIDs() (ids []int, err error) { func (s *MemoryForumStore) GetAllIDs() (ids []int, err error) {
s.forums.Range(func(_ interface{}, value interface{}) bool { s.forums.Range(func(_, val interface{}) bool {
ids = append(ids, value.(*Forum).ID) ids = append(ids, val.(*Forum).ID)
return true return true
}) })
sort.Ints(ids) sort.Ints(ids)
@ -299,7 +311,7 @@ func (s *MemoryForumStore) Delete(id int) error {
return err return err
} }
func (s *MemoryForumStore) AddTopic(tid int, uid int, fid int) error { func (s *MemoryForumStore) AddTopic(tid, uid, fid int) error {
_, err := s.updateCache.Exec(tid, uid, fid) _, err := s.updateCache.Exec(tid, uid, fid)
if err != nil { if err != nil {
return err return err
@ -351,7 +363,7 @@ func (s *MemoryForumStore) RemoveTopic(fid int) error {
} }
return s.RefreshTopic(fid) return s.RefreshTopic(fid)
} }
func (s *MemoryForumStore) RemoveTopics(fid int, count int) error { func (s *MemoryForumStore) RemoveTopics(fid, count int) error {
_, err := s.removeTopics.Exec(count, fid) _, err := s.removeTopics.Exec(count, fid)
if err != nil { if err != nil {
return err return err
@ -361,7 +373,7 @@ func (s *MemoryForumStore) RemoveTopics(fid int, count int) error {
// DEPRECATED. forum.Update() will be the way to do this in the future, once it's completed // DEPRECATED. forum.Update() will be the way to do this in the future, once it's completed
// TODO: Have a pointer to the last topic rather than storing it on the forum itself // TODO: Have a pointer to the last topic rather than storing it on the forum itself
func (s *MemoryForumStore) UpdateLastTopic(tid int, uid int, fid int) error { func (s *MemoryForumStore) UpdateLastTopic(tid, uid, fid int) error {
_, err := s.updateCache.Exec(tid, uid, fid) _, err := s.updateCache.Exec(tid, uid, fid)
if err != nil { if err != nil {
return err return err
@ -370,7 +382,7 @@ func (s *MemoryForumStore) UpdateLastTopic(tid int, uid int, fid int) error {
return s.Reload(fid) return s.Reload(fid)
} }
func (s *MemoryForumStore) Create(name string, desc string, active bool, preset string) (int, error) { func (s *MemoryForumStore) Create(name, desc string, active bool, preset string) (int, error) {
if name == "" { if name == "" {
return 0, ErrBlankName return 0, ErrBlankName
} }
@ -410,12 +422,12 @@ func (s *MemoryForumStore) UpdateOrder(updateMap map[int]int) error {
// ! Might be slightly inaccurate, if the sync.Map is constantly shifting and churning, but it'll stabilise eventually. Also, slow. Don't use this on every request x.x // ! Might be slightly inaccurate, if the sync.Map is constantly shifting and churning, but it'll stabilise eventually. Also, slow. Don't use this on every request x.x
// Length returns the number of forums in the memory cache // Length returns the number of forums in the memory cache
func (s *MemoryForumStore) Length() (length int) { func (s *MemoryForumStore) Length() (len int) {
s.forums.Range(func(_ interface{}, value interface{}) bool { s.forums.Range(func(_, _ interface{}) bool {
length++ len++
return true return true
}) })
return length return len
} }
// TODO: Get the total count of forums in the forum store rather than doing a heavy query for this? // TODO: Get the total count of forums in the forum store rather than doing a heavy query for this?

View File

@ -12,6 +12,7 @@ var Recalc RecalcInt
type RecalcInt interface { type RecalcInt interface {
Replies() (count int, err error) Replies() (count int, err error)
Forums() (count int, err error)
Subscriptions() (count int, err error) Subscriptions() (count int, err error)
ActivityStream() (count int, err error) ActivityStream() (count int, err error)
Users() error Users() error
@ -22,6 +23,8 @@ type DefaultRecalc struct {
getActivitySubscriptions *sql.Stmt getActivitySubscriptions *sql.Stmt
getActivityStream *sql.Stmt getActivityStream *sql.Stmt
getAttachments *sql.Stmt getAttachments *sql.Stmt
getTopicCount *sql.Stmt
resetTopicCount *sql.Stmt
} }
func NewDefaultRecalc(acc *qgen.Accumulator) (*DefaultRecalc, error) { func NewDefaultRecalc(acc *qgen.Accumulator) (*DefaultRecalc, error) {
@ -29,6 +32,10 @@ func NewDefaultRecalc(acc *qgen.Accumulator) (*DefaultRecalc, error) {
getActivitySubscriptions: acc.Select("activity_subscriptions").Columns("targetID,targetType").Prepare(), getActivitySubscriptions: acc.Select("activity_subscriptions").Columns("targetID,targetType").Prepare(),
getActivityStream: acc.Select("activity_stream").Columns("asid,event,elementID,elementType,extra").Prepare(), getActivityStream: acc.Select("activity_stream").Columns("asid,event,elementID,elementType,extra").Prepare(),
getAttachments: acc.Select("attachments").Columns("attachID,originID,originTable").Prepare(), getAttachments: acc.Select("attachments").Columns("attachID,originID,originTable").Prepare(),
getTopicCount: acc.Count("topics").Where("parentID=?").Prepare(),
//resetTopicCount: acc.SimpleUpdateSelect("forums", "topicCount = tc", "topics", "count(*) as tc", "parentID=?", "", ""),
// TODO: Avoid using RawPrepare
resetTopicCount: acc.RawPrepare("UPDATE forums, (SELECT COUNT(*) as tc FROM topics WHERE parentID=?) AS src SET forums.topicCount=src.tc WHERE forums.fid=?"),
}, acc.FirstError() }, acc.FirstError()
} }
@ -50,6 +57,18 @@ func (s *DefaultRecalc) Replies() (count int, err error) {
return count, err return count, err
} }
func (s *DefaultRecalc) Forums() (count int, err error) {
err = Forums.Each(func(f *Forum) error {
_, err := s.resetTopicCount.Exec(f.ID, f.ID)
if err != nil {
return err
}
count++
return nil
})
return count, err
}
func (s *DefaultRecalc) Subscriptions() (count int, err error) { func (s *DefaultRecalc) Subscriptions() (count int, err error) {
err = eachall(s.getActivitySubscriptions, func(r *sql.Rows) error { err = eachall(s.getActivitySubscriptions, func(r *sql.Rows) error {
var targetID int var targetID int

View File

@ -62,5 +62,5 @@ func (t *DefaultThaw) Thawed() bool {
} }
func (t *DefaultThaw) Thaw() { func (t *DefaultThaw) Thaw() {
atomic.StoreInt64(&t.thawed, 4) atomic.StoreInt64(&t.thawed, 3)
} }

View File

@ -44,6 +44,11 @@ type DefaultTopicList struct {
forums map[int]*ForumTopicListHolder forums map[int]*ForumTopicListHolder
forumLock sync.RWMutex forumLock sync.RWMutex
qcounts map[int]*sql.Stmt
qcounts2 map[int]*sql.Stmt
qLock sync.RWMutex
qLock2 sync.RWMutex
//permTree atomic.Value // [string(canSee)]canSee //permTree atomic.Value // [string(canSee)]canSee
//permTree map[string][]int // [string(canSee)]canSee //permTree map[string][]int // [string(canSee)]canSee
@ -59,6 +64,8 @@ func NewDefaultTopicList(acc *qgen.Accumulator) (*DefaultTopicList, error) {
oddGroups: make(map[int]*TopicListHolder), oddGroups: make(map[int]*TopicListHolder),
evenGroups: make(map[int]*TopicListHolder), evenGroups: make(map[int]*TopicListHolder),
forums: make(map[int]*ForumTopicListHolder), forums: make(map[int]*ForumTopicListHolder),
qcounts: make(map[int]*sql.Stmt),
qcounts2: make(map[int]*sql.Stmt),
getTopicsByForum: acc.Select("topics").Columns("tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, lastReplyID, views, postCount, likeCount").Where("parentID=?").Orderby("sticky DESC,lastReplyAt DESC,createdBy DESC").Limit("?,?").Prepare(), getTopicsByForum: acc.Select("topics").Columns("tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, lastReplyID, views, postCount, likeCount").Where("parentID=?").Orderby("sticky DESC,lastReplyAt DESC,createdBy DESC").Limit("?,?").Prepare(),
//getTidsByForum: acc.Select("topics").Columns("tid").Where("parentID=?").Orderby("sticky DESC,lastReplyAt DESC,createdBy DESC").Limit("?,?").Prepare(), //getTidsByForum: acc.Select("topics").Columns("tid").Where("parentID=?").Orderby("sticky DESC,lastReplyAt DESC,createdBy DESC").Limit("?,?").Prepare(),
} }
@ -119,12 +126,16 @@ func (tList *DefaultTopicList) Tick() error {
} }
canSeeHolders := make(map[string]*TopicListHolder) canSeeHolders := make(map[string]*TopicListHolder)
forumCounts := make(map[int]int)
for name, canSee := range permTree { for name, canSee := range permTree {
topicList, forumList, pagi, err := tList.GetListByCanSee(canSee, 1, 0, nil) topicList, forumList, pagi, err := tList.GetListByCanSee(canSee, 1, 0, nil)
if err != nil { if err != nil {
return err return err
} }
canSeeHolders[name] = &TopicListHolder{topicList, forumList, pagi} canSeeHolders[name] = &TopicListHolder{topicList, forumList, pagi}
if len(canSee) > 1 {
forumCounts[len(canSee)] += 1
}
} }
for gid, canSee := range gidToCanSee { for gid, canSee := range gidToCanSee {
addList(gid, canSeeHolders[canSee]) addList(gid, canSeeHolders[canSee])
@ -138,26 +149,80 @@ func (tList *DefaultTopicList) Tick() error {
tList.evenGroups = evenLists tList.evenGroups = evenLists
tList.evenLock.Unlock() tList.evenLock.Unlock()
topc := []int{0, 0, 0, 0, 0, 0}
addC := func(c int) {
lowI, low := 0, topc[0]
for i, top := range topc {
if top < low {
lowI = i
low = top
}
}
if c > low {
topc[lowI] = c
}
}
for forumCount := range forumCounts {
addC(forumCount)
}
qcounts := make(map[int]*sql.Stmt)
qcounts2 := make(map[int]*sql.Stmt)
for _, top := range topc {
if top == 0 {
continue
}
var qlist string
for i := 0; i < top; i++ {
if i != 0 {
qlist += ","
}
qlist += "?"
}
cols := "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data"
stmt, err := qgen.Builder.SimpleSelect("topics", cols, "parentID IN("+qlist+")", "views DESC,lastReplyAt DESC,createdBy DESC", "?,?")
if err != nil {
return err
}
qcounts[top] = stmt
stmt, err = qgen.Builder.SimpleSelect("topics", cols, "parentID IN("+qlist+")", "sticky DESC,lastReplyAt DESC,createdBy DESC", "?,?")
if err != nil {
return err
}
qcounts2[top] = stmt
}
tList.qLock.Lock()
tList.qcounts = qcounts
tList.qLock.Unlock()
tList.qLock2.Lock()
tList.qcounts2 = qcounts2
tList.qLock2.Unlock()
forums, err := Forums.GetAll() forums, err := Forums.GetAll()
if err != nil { if err != nil {
return err return err
} }
top5 := []*Forum{nil, nil, nil, nil, nil} top8 := []*Forum{nil, nil, nil, nil, nil, nil, nil, nil}
z := true z := true
addScore2 := func(f *Forum) { addScore2 := func(f *Forum) {
for i, top := range top5 { for i, top := range top8 {
if top.TopicCount < f.TopicCount { if top.TopicCount < f.TopicCount {
top5[i] = f top8[i] = f
return return
} }
} }
} }
addScore := func(f *Forum) { addScore := func(f *Forum) {
if z { if z {
for i, top := range top5 { for i, top := range top8 {
if top == nil { if top == nil {
top5[i] = f top8[i] = f
return return
} }
} }
@ -178,7 +243,7 @@ func (tList *DefaultTopicList) Tick() error {
} }
addScore(f) addScore(f)
} }
for _, f := range top5 { for _, f := range top8 {
if f != nil { if f != nil {
fshort = append(fshort, f) fshort = append(fshort, f)
} }
@ -209,6 +274,11 @@ func (tList *DefaultTopicList) GetListByForum(f *Forum, page, orderby int) (topi
if page == 0 { if page == 0 {
page = 1 page = 1
} }
if f.TopicCount == 0 {
_, page, lastPage := PageOffset(f.TopicCount, page, Config.ItemsPerPage)
pageList := Paginate(page, lastPage, 5)
return topicList, Paginator{pageList, page, lastPage}, nil
}
if page == 1 && orderby == 0 { if page == 1 && orderby == 0 {
var h *ForumTopicListHolder var h *ForumTopicListHolder
var ok bool var ok bool
@ -419,21 +489,33 @@ func (tList *DefaultTopicList) GetList(page, orderby int, filterIDs []int) (topi
func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []interface{}, qlist string) (topicList []*TopicsRow, paginator Paginator, err error) { func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []interface{}, qlist string) (topicList []*TopicsRow, paginator Paginator, err error) {
//log.Printf("argList: %+v\n",argList) //log.Printf("argList: %+v\n",argList)
//log.Printf("qlist: %+v\n",qlist) //log.Printf("qlist: %+v\n",qlist)
var orderq string var orderq string
var stmt *sql.Stmt
if orderby == TopicListMostViewed { if orderby == TopicListMostViewed {
orderq = "views DESC,lastReplyAt DESC,createdBy DESC" tList.qLock.RLock()
stmt = tList.qcounts[len(argList)-2]
tList.qLock.RUnlock()
if stmt == nil {
orderq = "views DESC,lastReplyAt DESC,createdBy DESC"
}
} else { } else {
orderq = "sticky DESC,lastReplyAt DESC,createdBy DESC" tList.qLock2.RLock()
stmt = tList.qcounts2[len(argList)-2]
tList.qLock2.RUnlock()
if stmt == nil {
orderq = "sticky DESC,lastReplyAt DESC,createdBy DESC"
}
} }
offset, page, lastPage := PageOffset(topicCount, page, Config.ItemsPerPage) offset, page, lastPage := PageOffset(topicCount, page, Config.ItemsPerPage)
// TODO: Prepare common qlist lengths to speed this up in common cases, prepared statements are prepared lazily anyway, so it probably doesn't matter if we do ten or so // TODO: Prepare common qlist lengths to speed this up in common cases, prepared statements are prepared lazily anyway, so it probably doesn't matter if we do ten or so
stmt, err := qgen.Builder.SimpleSelect("topics", "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data", "parentID IN("+qlist+")", orderq, "?,?") if stmt == nil {
if err != nil { stmt, err = qgen.Builder.SimpleSelect("topics", "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data", "parentID IN("+qlist+")", orderq, "?,?")
return nil, Paginator{nil, 1, 1}, err if err != nil {
return nil, Paginator{nil, 1, 1}, err
}
defer stmt.Close()
} }
defer stmt.Close()
argList = append(argList, offset) argList = append(argList, offset)
argList = append(argList, Config.ItemsPerPage) argList = append(argList, Config.ItemsPerPage)

View File

@ -131,27 +131,27 @@ type Adapter interface {
DbVersion() string DbVersion() string
DropTable(name, table string) (string, error) DropTable(name, table string) (string, error)
CreateTable(name, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) CreateTable(name, table, charset, collation string, cols []DBTableColumn, keys []DBTableKey) (string, error)
// TODO: Some way to add indices and keys // TODO: Some way to add indices and keys
// TODO: Test this // TODO: Test this
AddColumn(name, table string, column DBTableColumn, key *DBTableKey) (string, error) AddColumn(name, table string, col DBTableColumn, key *DBTableKey) (string, error)
DropColumn(name, table, colname string) (string, error) DropColumn(name, table, colname string) (string, error)
RenameColumn(name, table, oldName, newName string) (string, error) RenameColumn(name, table, oldName, newName string) (string, error)
ChangeColumn(name, table, colName string, col DBTableColumn) (string, error) ChangeColumn(name, table, colName string, col DBTableColumn) (string, error)
SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error) SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error)
AddIndex(name, table, iname, colname string) (string, error) AddIndex(name, table, iname, colname string) (string, error)
AddKey(name, table, column string, key DBTableKey) (string, error) AddKey(name, table, col string, key DBTableKey) (string, error)
RemoveIndex(name, table, column string) (string, error) RemoveIndex(name, table, col string) (string, error)
AddForeignKey(name, table, column, ftable, fcolumn string, cascade bool) (out string, e error) AddForeignKey(name, table, col, ftable, fcolumn string, cascade bool) (out string, e error)
SimpleInsert(name, table, columns, fields string) (string, error) SimpleInsert(name, table, cols, fields string) (string, error)
SimpleBulkInsert(name, table, columns string, fieldSet []string) (string, error) SimpleBulkInsert(name, table, cols string, fieldSet []string) (string, error)
SimpleUpdate(b *updatePrebuilder) (string, error) SimpleUpdate(b *updatePrebuilder) (string, error)
SimpleUpdateSelect(b *updatePrebuilder) (string, error) // ! Experimental SimpleUpdateSelect(b *updatePrebuilder) (string, error) // ! Experimental
SimpleDelete(name, table, where string) (string, error) SimpleDelete(name, table, where string) (string, error)
Purge(name, table string) (string, error) Purge(name, table string) (string, error)
SimpleSelect(name, table, columns, where, orderby, limit string) (string, error) SimpleSelect(name, table, cols, where, orderby, limit string) (string, error)
ComplexDelete(b *deletePrebuilder) (string, error) ComplexDelete(b *deletePrebuilder) (string, error)
SimpleLeftJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error) SimpleLeftJoin(name, table1, table2, cols, joiners, where, orderby, limit string) (string, error)
SimpleInnerJoin(string, string, string, string, string, string, string, string) (string, error) SimpleInnerJoin(string, string, string, string, string, string, string, string) (string, error)
SimpleInsertSelect(string, DBInsert, DBSelect) (string, error) SimpleInsertSelect(string, DBInsert, DBSelect) (string, error)
SimpleInsertLeftJoin(string, DBInsert, DBJoin) (string, error) SimpleInsertLeftJoin(string, DBInsert, DBJoin) (string, error)

View File

@ -2,12 +2,12 @@
<div class="pageset"> <div class="pageset">
{{if gt .Page 1}}<div class="pageitem pagefirst"><a href="?page=1" aria-label="{{lang "paginator.first_page_aria"}}">{{lang "paginator.first_page"}}</a></div> {{if gt .Page 1}}<div class="pageitem pagefirst"><a href="?page=1" aria-label="{{lang "paginator.first_page_aria"}}">{{lang "paginator.first_page"}}</a></div>
<div class="pageitem pageprev"><a href="?page={{subtract .Page 1}}" rel="prev" aria-label="{{lang "paginator.prev_page_aria"}}">{{lang "paginator.prev_page"}}</a></div> <div class="pageitem pageprev"><a href="?page={{subtract .Page 1}}" rel="prev" aria-label="{{lang "paginator.prev_page_aria"}}">{{lang "paginator.prev_page"}}</a></div>
<link rel="prev" href="?page={{subtract .Page 1}}" />{{end}} <link rel="prev" href="?page={{subtract .Page 1}}"/>{{end}}
{{range .PageList}} {{range .PageList}}
<div class="pageitem{{if eq . $.Page}} pagecurrent{{end}}"><a href="?page={{.}}">{{.}}</a></div> <div class="pageitem{{if eq . $.Page}} pagecurrent{{end}}"><a href="?page={{.}}">{{.}}</a></div>
{{end}} {{end}}
{{if ne .LastPage .Page}} {{if ne .LastPage .Page}}
<link rel="next" href="?page={{add .Page 1}}" /> <link rel="next" href="?page={{add .Page 1}}"/>
<div class="pageitem pagenext"><a href="?page={{add .Page 1}}" rel="next" aria-label="{{lang "paginator.next_page_aria"}}">{{lang "paginator.next_page"}}</a></div> <div class="pageitem pagenext"><a href="?page={{add .Page 1}}" rel="next" aria-label="{{lang "paginator.next_page_aria"}}">{{lang "paginator.next_page"}}</a></div>
<div class="pageitem pagelast"><a href="?page={{.LastPage}}" aria-label="{{lang "paginator.last_page_aria"}}">{{lang "paginator.last_page"}}</a></div>{{end}} <div class="pageitem pagelast"><a href="?page={{.LastPage}}" aria-label="{{lang "paginator.last_page_aria"}}">{{lang "paginator.last_page"}}</a></div>{{end}}
</div> </div>

View File

@ -267,6 +267,12 @@ func sched() error {
} }
log.Printf("Deleted %d orphaned replies.", count) log.Printf("Deleted %d orphaned replies.", count)
count, err = c.Recalc.Forums()
if err != nil {
return errors.WithStack(err)
}
log.Printf("Recalculated %d forum topic counts.", count)
count, err = c.Recalc.Subscriptions() count, err = c.Recalc.Subscriptions()
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)