6870d242e9
Add LockMany() to TopicStore. Add RemoveMany() to TopicCache. Add ClearIPs() to PollStore. Add Purge() to RegLogStore. Add DeleteOlderThanDays() to RegLogStore. Add Purge() to LoginLogStore. Add DeleteOlderThanDays() to LoginLogStore. Add SetInt() to MetaStore. Add SetInt64() to MetaStore. Use Createf() in RegLogItem.Create() Use Count() in SQLRegLogStore.Count() Use Count() in SQLLoginLogStore.Count() Use Countf() in SQLLoginLogStore.CountUser() Add trailing triple dot parser test case. Removed a block of commented code in gen router. Reduce boilerplate.
246 lines
5.9 KiB
Go
246 lines
5.9 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.SimpleCount(p, "", ""),
|
|
|
|
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) 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
|
|
}
|