cff2e96915
show daily task count on debug page optimise count queries on debug page use Count() methods on debug page where ever possible
250 lines
6.0 KiB
Go
250 lines
6.0 KiB
Go
package common
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"strconv"
|
|
|
|
qgen "github.com/Azareal/Gosora/query_gen"
|
|
)
|
|
|
|
var Polls PollStore
|
|
|
|
type PollOption struct {
|
|
ID int
|
|
Value string
|
|
}
|
|
|
|
type Pollable interface {
|
|
GetID() int
|
|
GetTable() string
|
|
SetPoll(pollID int) error
|
|
}
|
|
|
|
type PollStore interface {
|
|
Get(id int) (*Poll, error)
|
|
Exists(id int) bool
|
|
ClearIPs() error
|
|
Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error)
|
|
Reload(id int) error
|
|
Count() int
|
|
|
|
SetCache(cache PollCache)
|
|
GetCache() PollCache
|
|
}
|
|
|
|
type DefaultPollStore struct {
|
|
cache PollCache
|
|
|
|
get *sql.Stmt
|
|
exists *sql.Stmt
|
|
createPoll *sql.Stmt
|
|
createPollOption *sql.Stmt
|
|
delete *sql.Stmt
|
|
count *sql.Stmt
|
|
|
|
clearIPs *sql.Stmt
|
|
}
|
|
|
|
func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
|
|
acc := qgen.NewAcc()
|
|
if cache == nil {
|
|
cache = NewNullPollCache()
|
|
}
|
|
// TODO: Add an admin version of registerStmt with more flexibility?
|
|
p := "polls"
|
|
return &DefaultPollStore{
|
|
cache: cache,
|
|
get: acc.Select(p).Columns("parentID,parentTable,type,options,votes").Where("pollID=?").Stmt(),
|
|
exists: acc.Select(p).Columns("pollID").Where("pollID=?").Stmt(),
|
|
createPoll: acc.Insert(p).Columns("parentID,parentTable,type,options").Fields("?,?,?,?").Prepare(),
|
|
createPollOption: acc.Insert("polls_options").Columns("pollID,option,votes").Fields("?,?,0").Prepare(),
|
|
count: acc.Count(p).Prepare(),
|
|
|
|
clearIPs: acc.Update("polls_votes").Set("ip=''").Where("ip!=''").Stmt(),
|
|
}, acc.FirstError()
|
|
}
|
|
|
|
func (s *DefaultPollStore) Exists(id int) bool {
|
|
e := s.exists.QueryRow(id).Scan(&id)
|
|
if e != nil && e != ErrNoRows {
|
|
LogError(e)
|
|
}
|
|
return e != ErrNoRows
|
|
}
|
|
|
|
func (s *DefaultPollStore) Get(id int) (*Poll, error) {
|
|
p, err := s.cache.Get(id)
|
|
if err == nil {
|
|
return p, nil
|
|
}
|
|
|
|
p = &Poll{ID: id}
|
|
var optionTxt []byte
|
|
err = s.get.QueryRow(id).Scan(&p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = json.Unmarshal(optionTxt, &p.Options)
|
|
if err == nil {
|
|
p.QuickOptions = s.unpackOptionsMap(p.Options)
|
|
s.cache.Set(p)
|
|
}
|
|
return p, err
|
|
}
|
|
|
|
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
|
|
// TODO: ID of 0 should always error?
|
|
func (s *DefaultPollStore) BulkGetMap(ids []int) (list map[int]*Poll, err error) {
|
|
idCount := len(ids)
|
|
list = make(map[int]*Poll)
|
|
if idCount == 0 {
|
|
return list, nil
|
|
}
|
|
|
|
var stillHere []int
|
|
sliceList := s.cache.BulkGet(ids)
|
|
for i, sliceItem := range sliceList {
|
|
if sliceItem != nil {
|
|
list[sliceItem.ID] = sliceItem
|
|
} else {
|
|
stillHere = append(stillHere, ids[i])
|
|
}
|
|
}
|
|
ids = stillHere
|
|
|
|
// If every user is in the cache, then return immediately
|
|
if len(ids) == 0 {
|
|
return list, nil
|
|
}
|
|
|
|
idList, q := inqbuild(ids)
|
|
rows, err := qgen.NewAcc().Select("polls").Columns("pollID,parentID,parentTable,type,options,votes").Where("pollID IN(" + q + ")").Query(idList...)
|
|
if err != nil {
|
|
return list, err
|
|
}
|
|
|
|
for rows.Next() {
|
|
p := &Poll{ID: 0}
|
|
var optionTxt []byte
|
|
err := rows.Scan(&p.ID, &p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount)
|
|
if err != nil {
|
|
return list, err
|
|
}
|
|
|
|
err = json.Unmarshal(optionTxt, &p.Options)
|
|
if err != nil {
|
|
return list, err
|
|
}
|
|
p.QuickOptions = s.unpackOptionsMap(p.Options)
|
|
s.cache.Set(p)
|
|
|
|
list[p.ID] = p
|
|
}
|
|
|
|
// Did we miss any polls?
|
|
if idCount > len(list) {
|
|
var sidList string
|
|
for _, id := range ids {
|
|
if _, ok := list[id]; !ok {
|
|
sidList += strconv.Itoa(id) + ","
|
|
}
|
|
}
|
|
|
|
// We probably don't need this, but it might be useful in case of bugs in BulkCascadeGetMap
|
|
if sidList == "" {
|
|
// TODO: Bulk log this
|
|
if Dev.DebugMode {
|
|
log.Print("This data is sampled later in the BulkCascadeGetMap function, so it might miss the cached IDs")
|
|
log.Print("idCount", idCount)
|
|
log.Print("ids", ids)
|
|
log.Print("list", list)
|
|
}
|
|
return list, errors.New("We weren't able to find a poll, but we don't know which one")
|
|
}
|
|
sidList = sidList[0 : len(sidList)-1]
|
|
|
|
err = errors.New("Unable to find the polls with the following IDs: " + sidList)
|
|
}
|
|
|
|
return list, err
|
|
}
|
|
|
|
func (s *DefaultPollStore) Reload(id int) error {
|
|
p := &Poll{ID: id}
|
|
var optionTxt []byte
|
|
e := s.get.QueryRow(id).Scan(&p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount)
|
|
if e != nil {
|
|
_ = s.cache.Remove(id)
|
|
return e
|
|
}
|
|
e = json.Unmarshal(optionTxt, &p.Options)
|
|
if e != nil {
|
|
_ = s.cache.Remove(id)
|
|
return e
|
|
}
|
|
p.QuickOptions = s.unpackOptionsMap(p.Options)
|
|
_ = s.cache.Set(p)
|
|
return nil
|
|
}
|
|
|
|
func (s *DefaultPollStore) unpackOptionsMap(rawOptions map[int]string) []PollOption {
|
|
opts := make([]PollOption, len(rawOptions))
|
|
for id, opt := range rawOptions {
|
|
opts[id] = PollOption{id, opt}
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func (s *DefaultPollStore) ClearIPs() error {
|
|
_, e := s.clearIPs.Exec()
|
|
return e
|
|
}
|
|
|
|
// TODO: Use a transaction for this
|
|
func (s *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions map[int]string) (id int, e error) {
|
|
// TODO: Move the option names into the polls_options table and get rid of this json sludge?
|
|
pollOptionsTxt, e := json.Marshal(pollOptions)
|
|
if e != nil {
|
|
return 0, e
|
|
}
|
|
res, e := s.createPoll.Exec(parent.GetID(), parent.GetTable(), pollType, pollOptionsTxt)
|
|
if e != nil {
|
|
return 0, e
|
|
}
|
|
lastID, e := res.LastInsertId()
|
|
if e != nil {
|
|
return 0, e
|
|
}
|
|
|
|
for i := 0; i < len(pollOptions); i++ {
|
|
_, e := s.createPollOption.Exec(lastID, i)
|
|
if e != nil {
|
|
return 0, e
|
|
}
|
|
}
|
|
|
|
id = int(lastID)
|
|
return id, parent.SetPoll(id) // TODO: Delete the poll (and options) if SetPoll fails
|
|
}
|
|
|
|
func (s *DefaultPollStore) Count() int {
|
|
return Count(s.count)
|
|
}
|
|
|
|
func (s *DefaultPollStore) SetCache(cache PollCache) {
|
|
s.cache = cache
|
|
}
|
|
|
|
// TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it.
|
|
func (s *DefaultPollStore) GetCache() PollCache {
|
|
_, ok := s.cache.(*NullPollCache)
|
|
if ok {
|
|
return nil
|
|
}
|
|
return s.cache
|
|
}
|